Merge "Revert "Revert "Add profman tool: responsible to process profiles"""
diff --git a/Android.mk b/Android.mk
index 2e05d33..e762814 100644
--- a/Android.mk
+++ b/Android.mk
@@ -86,6 +86,7 @@
 include $(art_path)/oatdump/Android.mk
 include $(art_path)/imgdiag/Android.mk
 include $(art_path)/patchoat/Android.mk
+include $(art_path)/profman/Android.mk
 include $(art_path)/dalvikvm/Android.mk
 include $(art_path)/tools/Android.mk
 include $(art_path)/tools/ahat/Android.mk
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk
index 704d69a..22c0e8d 100644
--- a/build/Android.gtest.mk
+++ b/build/Android.gtest.mk
@@ -144,6 +144,12 @@
   $(TARGET_CORE_IMAGE_default_no-pic_32) \
   oatdump
 
+# Profile assistant tests requires profman utility.
+ART_GTEST_profile_assistant_test_HOST_DEPS := \
+  $(HOST_OUT_EXECUTABLES)/profmand
+ART_GTEST_profile_assistant_test_TARGET_DEPS := \
+  profman
+
 # The path for which all the source files are relative, not actually the current directory.
 LOCAL_PATH := art
 
@@ -153,6 +159,7 @@
   dexlist/dexlist_test.cc \
   imgdiag/imgdiag_test.cc \
   oatdump/oatdump_test.cc \
+  profman/profile_assistant_test.cc \
   runtime/arch/arch_test.cc \
   runtime/arch/instruction_set_test.cc \
   runtime/arch/instruction_set_features_test.cc \
@@ -270,7 +277,6 @@
   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/Android.mk b/compiler/Android.mk
index 159e9cf..3f61e8e 100644
--- a/compiler/Android.mk
+++ b/compiler/Android.mk
@@ -107,8 +107,7 @@
 	elf_writer.cc \
 	elf_writer_quick.cc \
 	image_writer.cc \
-	oat_writer.cc \
-	profile_assistant.cc
+	oat_writer.cc
 
 LIBART_COMPILER_SRC_FILES_arm := \
 	dex/quick/arm/assemble_arm.cc \
diff --git a/compiler/profile_assistant.cc b/compiler/profile_assistant.cc
deleted file mode 100644
index 85335ef..0000000
--- a/compiler/profile_assistant.cc
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright (C) 2015 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 "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::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() ||
-      (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 < 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) {
-    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.
-    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 (!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 (!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) {
-  *profile_compilation_info = nullptr;
-
-  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) {
-  *profile_compilation_info = nullptr;
-
-  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
deleted file mode 100644
index ad5e216..0000000
--- a/compiler/profile_assistant.h
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2015 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.
- */
-
-#ifndef ART_COMPILER_PROFILE_ASSISTANT_H_
-#define ART_COMPILER_PROFILE_ASSISTANT_H_
-
-#include <string>
-#include <vector>
-
-#include "base/scoped_flock.h"
-#include "jit/offline_profiling_info.cc"
-
-namespace art {
-
-class ProfileAssistant {
- public:
-  // Process the profile information present in the given files. Returns true
-  // if the analysis ended up successfully (i.e. no errors during reading,
-  // merging or writing of profile files).
-  //
-  // If the returned value is true and there is a significant difference between
-  // profile_files and reference_profile_files:
-  //   - profile_compilation_info is set to a not null object that
-  //     can be used to drive compilation. It will be the merge of all the data
-  //     found in profile_files and reference_profile_files.
-  //   - the data from profile_files[i] is merged into
-  //     reference_profile_files[i] and the corresponding backing file is
-  //     updated.
-  //
-  // If the returned value is false or the difference is insignificant,
-  // profile_compilation_info will be set to null.
-  //
-  // Additional notes:
-  //   - as mentioned above, this function may update the content of the files
-  //     passed with the reference_profile_files.
-  //   - if reference_profile_files is not empty it must be the same size as
-  //     profile_files.
-  static bool ProcessProfiles(
-      const std::vector<std::string>& profile_files,
-      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);
-};
-
-}  // namespace art
-
-#endif  // ART_COMPILER_PROFILE_ASSISTANT_H_
diff --git a/compiler/profile_assistant_test.cc b/compiler/profile_assistant_test.cc
deleted file mode 100644
index 58b7513..0000000
--- a/compiler/profile_assistant_test.cc
+++ /dev/null
@@ -1,279 +0,0 @@
-/*
- * 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 = nullptr;
-  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 = nullptr;
-  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 = nullptr;
-  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 541fb5a..f7efdc5 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -40,6 +40,7 @@
 #include "art_method-inl.h"
 #include "base/dumpable.h"
 #include "base/macros.h"
+#include "base/scoped_flock.h"
 #include "base/stl_util.h"
 #include "base/stringpiece.h"
 #include "base/time_utils.h"
@@ -71,7 +72,6 @@
 #include "mirror/object_array-inl.h"
 #include "oat_writer.h"
 #include "os.h"
-#include "profile_assistant.h"
 #include "runtime.h"
 #include "runtime_options.h"
 #include "ScopedLocalRef.h"
@@ -339,23 +339,10 @@
   UsageError("      Example: --runtime-arg -Xms256m");
   UsageError("");
   UsageError("  --profile-file=<filename>: specify profiler output file to use for compilation.");
-  UsageError("      Can be specified multiple time, in which case the data from the different");
-  UsageError("      profiles will be aggregated.");
-  UsageError("");
-  UsageError("  --reference-profile-file=<filename>: specify a reference profile file to use when");
-  UsageError("      compiling. The data in this file will be compared with the data in the");
-  UsageError("      associated --profile-file and the compilation will proceed only if there is");
-  UsageError("      a significant difference (--reference-profile-file is paired with");
-  UsageError("      --profile-file in the natural order). If the compilation was attempted then");
-  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.");
@@ -517,14 +504,6 @@
   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) :
@@ -567,10 +546,12 @@
       dump_passes_(false),
       dump_timing_(false),
       dump_slow_timing_(kIsDebugBuild),
-      swap_fd_(-1),
+      swap_fd_(kInvalidFd),
       app_image_fd_(kInvalidFd),
+      profile_file_fd_(kInvalidFd),
       timings_(timings),
-      force_determinism_(false) {}
+      force_determinism_(false)
+      {}
 
   ~Dex2Oat() {
     // Log completion time before deleting the runtime_, because this accesses
@@ -824,25 +805,8 @@
       }
     }
 
-    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 (!profile_file_.empty() && (profile_file_fd_ != kInvalidFd)) {
+      Usage("Profile file should not be specified with both --profile-file-fd and --profile-file");
     }
 
     if (!parser_options->oat_symbols.empty()) {
@@ -1153,16 +1117,9 @@
       } else if (option.starts_with("--compiler-backend=")) {
         ParseCompilerBackend(option, parser_options.get());
       } else if (option.starts_with("--profile-file=")) {
-        profile_files_.push_back(option.substr(strlen("--profile-file=")).ToString());
-      } else if (option.starts_with("--reference-profile-file=")) {
-        reference_profile_files_.push_back(
-            option.substr(strlen("--reference-profile-file=")).ToString());
+        profile_file_ = option.substr(strlen("--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
+        ParseUintOption(option, "--profile-file-fd", &profile_file_fd_, Usage);
       } else if (option == "--host") {
         is_host_ = true;
       } else if (option == "--runtime-arg") {
@@ -1865,33 +1822,44 @@
   }
 
   bool UseProfileGuidedCompilation() const {
-    return !profile_files_.empty() || !profile_files_fd_.empty();
+    return !profile_file_.empty() || (profile_file_fd_ != kInvalidFd);
   }
 
-  bool ProcessProfiles() {
+  bool LoadProfile() {
     DCHECK(UseProfileGuidedCompilation());
-    ProfileCompilationInfo* info = nullptr;
-    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_");
+
+    profile_compilation_info_.reset(new ProfileCompilationInfo());
+    ScopedFlock flock;
+    bool success = false;
+    std::string error;
+    if (profile_file_fd_ != -1) {
+      // The file doesn't need to be flushed so don't check the usage.
+      // Pass a bogus path so that we can easily attribute any reported error.
+      File file(profile_file_fd_, "profile", /*check_usage*/ false, /*read_only_mode*/ true);
+      if (flock.Init(&file, &error)) {
+        success = profile_compilation_info_->Load(profile_file_fd_);
+      }
     } else {
-      result = ProfileAssistant::ProcessProfiles(
-          profile_files_, reference_profile_files_, &info);
+      if (flock.Init(profile_file_.c_str(), O_RDONLY, /* block */ true, &error)) {
+        success = profile_compilation_info_->Load(flock.GetFile()->Fd());
+      }
+    }
+    if (!error.empty()) {
+      LOG(WARNING) << "Cannot lock profiles: " << error;
     }
 
-    profile_compilation_info_.reset(info);
+    if (!success) {
+      profile_compilation_info_.reset(nullptr);
+    }
 
-    return result;
+    return success;
   }
 
   bool ShouldCompileBasedOnProfiles() const {
     DCHECK(UseProfileGuidedCompilation());
-    // If we are given profiles, compile only if we have new information.
-    return profile_compilation_info_ != nullptr;
+    // If we are given a profile, compile only if we have some data in it.
+    return (profile_compilation_info_ != nullptr) &&
+        (profile_compilation_info_->GetNumberOfMethods() != 0);
   }
 
  private:
@@ -2460,10 +2428,8 @@
   int swap_fd_;
   std::string app_image_file_name_;
   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::string profile_file_;
+  int profile_file_fd_;
   std::unique_ptr<ProfileCompilationInfo> profile_compilation_info_;
   TimingLogger* timings_;
   std::unique_ptr<CumulativeLogger> compiler_phases_timings_;
@@ -2592,7 +2558,7 @@
   // Process profile information and assess if we need to do a profile guided compilation.
   // This operation involves I/O.
   if (dex2oat.UseProfileGuidedCompilation()) {
-    if (dex2oat.ProcessProfiles()) {
+    if (dex2oat.LoadProfile()) {
       if (!dex2oat.ShouldCompileBasedOnProfiles()) {
         LOG(INFO) << "Skipped compilation because of insignificant profile delta";
         return EXIT_SUCCESS;
diff --git a/profman/Android.mk b/profman/Android.mk
new file mode 100644
index 0000000..d38d107
--- /dev/null
+++ b/profman/Android.mk
@@ -0,0 +1,45 @@
+#
+# 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include art/build/Android.executable.mk
+
+PROFMAN_SRC_FILES := \
+	profman.cc \
+	profile_assistant.cc
+
+# TODO: Remove this when the framework (installd) supports pushing the
+# right instruction-set parameter for the primary architecture.
+ifneq ($(filter ro.zygote=zygote64,$(PRODUCT_DEFAULT_PROPERTY_OVERRIDES)),)
+  profman_arch := 64
+else
+  profman_arch := 32
+endif
+
+ifeq ($(ART_BUILD_TARGET_NDEBUG),true)
+  $(eval $(call build-art-executable,profman,$(PROFMAN_SRC_FILES),libcutils,art/profman,target,ndebug,$(profman_arch)))
+endif
+ifeq ($(ART_BUILD_TARGET_DEBUG),true)
+  $(eval $(call build-art-executable,profman,$(PROFMAN_SRC_FILES),libcutils,art/profman,target,debug,$(profman_arch)))
+endif
+
+ifeq ($(ART_BUILD_HOST_NDEBUG),true)
+  $(eval $(call build-art-executable,profman,$(PROFMAN_SRC_FILES),libcutils,art/profman,host,ndebug))
+endif
+ifeq ($(ART_BUILD_HOST_DEBUG),true)
+  $(eval $(call build-art-executable,profman,$(PROFMAN_SRC_FILES),libcutils,art/profman,host,debug))
+endif
diff --git a/profman/profile_assistant.cc b/profman/profile_assistant.cc
new file mode 100644
index 0000000..58e8a3a
--- /dev/null
+++ b/profman/profile_assistant.cc
@@ -0,0 +1,159 @@
+/*
+ * 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 "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;
+
+ProfileAssistant::ProcessingResult ProfileAssistant::ProcessProfilesInternal(
+        const std::vector<ScopedFlock>& profile_files,
+        const ScopedFlock& reference_profile_file) {
+  DCHECK(!profile_files.empty());
+
+  std::vector<ProfileCompilationInfo> new_info(profile_files.size());
+  bool should_compile = false;
+  // Read the main profile files.
+  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 kErrorBadProfiles;
+    }
+    // Do we have enough new profiled methods that will make the compilation worthwhile?
+    should_compile |= (new_info[i].GetNumberOfMethods() > kMinNewMethodsForCompilation);
+  }
+
+  if (!should_compile) {
+    return kSkipCompilation;
+  }
+
+  // Merge information.
+  ProfileCompilationInfo info;
+  if (!info.Load(reference_profile_file.GetFile()->Fd())) {
+    LOG(WARNING) << "Could not load reference profile file";
+    return kErrorBadProfiles;
+  }
+
+  for (size_t i = 0; i < new_info.size(); i++) {
+    // Merge all data into a single object.
+    if (!info.Load(new_info[i])) {
+      LOG(WARNING) << "Could not merge profile data at index " << i;
+      return kErrorBadProfiles;
+    }
+  }
+  // We were successful in merging all profile information. Update the reference profile.
+  if (!reference_profile_file.GetFile()->ClearContent()) {
+    PLOG(WARNING) << "Could not clear reference profile file";
+    return kErrorIO;
+  }
+  if (!info.Save(reference_profile_file.GetFile()->Fd())) {
+    LOG(WARNING) << "Could not save reference profile file";
+    return kErrorIO;
+  }
+
+  return kCompile;
+}
+
+static bool InitFlock(const std::string& filename, ScopedFlock& flock, std::string* error) {
+  return flock.Init(filename.c_str(), O_RDWR, /* block */ true, error);
+}
+
+static bool InitFlock(int fd, ScopedFlock& flock, std::string* error) {
+  DCHECK_GE(fd, 0);
+  // We do not own the descriptor, so disable auto-close and don't check usage.
+  File file(fd, false);
+  file.DisableAutoClose();
+  return flock.Init(&file, error);
+}
+
+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 (!InitFlock(filenames[i], flocks_[i], error)) {
+        *error += " (index=" + std::to_string(i) + ")";
+        return false;
+      }
+    }
+    return true;
+  }
+
+  // Will block until all the locks are acquired.
+  bool Init(const std::vector<int>& fds, /* out */ std::string* error) {
+    for (size_t i = 0; i < fds.size(); i++) {
+      DCHECK_GE(fds[i], 0);
+      if (!InitFlock(fds[i], flocks_[i], error)) {
+        *error += " (index=" + std::to_string(i) + ")";
+        return false;
+      }
+    }
+    return true;
+  }
+
+  const std::vector<ScopedFlock>& Get() const { return flocks_; }
+
+ private:
+  std::vector<ScopedFlock> flocks_;
+};
+
+ProfileAssistant::ProcessingResult ProfileAssistant::ProcessProfiles(
+        const std::vector<int>& profile_files_fd,
+        int reference_profile_file_fd) {
+  DCHECK_GE(reference_profile_file_fd, 0);
+  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 kErrorCannotLock;
+  }
+  ScopedFlock reference_profile_file_flock;
+  if (!InitFlock(reference_profile_file_fd, reference_profile_file_flock, &error)) {
+    LOG(WARNING) << "Could not lock reference profiled files: " << error;
+    return kErrorCannotLock;
+  }
+
+  return ProcessProfilesInternal(profile_files_flocks.Get(),
+                                 reference_profile_file_flock);
+}
+
+ProfileAssistant::ProcessingResult ProfileAssistant::ProcessProfiles(
+        const std::vector<std::string>& profile_files,
+        const std::string& reference_profile_file) {
+  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 kErrorCannotLock;
+  }
+  ScopedFlock reference_profile_file_flock;
+  if (!InitFlock(reference_profile_file, reference_profile_file_flock, &error)) {
+    LOG(WARNING) << "Could not lock reference profile files: " << error;
+    return kErrorCannotLock;
+  }
+
+  return ProcessProfilesInternal(profile_files_flocks.Get(),
+                                 reference_profile_file_flock);
+}
+
+}  // namespace art
diff --git a/profman/profile_assistant.h b/profman/profile_assistant.h
new file mode 100644
index 0000000..d3c75b8
--- /dev/null
+++ b/profman/profile_assistant.h
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+
+#ifndef ART_PROFMAN_PROFILE_ASSISTANT_H_
+#define ART_PROFMAN_PROFILE_ASSISTANT_H_
+
+#include <string>
+#include <vector>
+
+#include "base/scoped_flock.h"
+#include "jit/offline_profiling_info.h"
+
+namespace art {
+
+class ProfileAssistant {
+ public:
+  // These also serve as return codes of profman and are processed by installd
+  // (frameworks/native/cmds/installd/commands.cpp)
+  enum ProcessingResult {
+    kCompile = 0,
+    kSkipCompilation = 1,
+    kErrorBadProfiles = 2,
+    kErrorIO = 3,
+    kErrorCannotLock = 4
+  };
+
+  // Process the profile information present in the given files. Returns one of
+  // ProcessingResult values depending on profile information and whether or not
+  // the analysis ended up successfully (i.e. no errors during reading,
+  // merging or writing of profile files).
+  //
+  // When the returned value is kCompile there is a significant difference
+  // between profile_files and reference_profile_files. In this case
+  // reference_profile will be updated with the profiling info obtain after
+  // merging all profiles.
+  //
+  // When the returned value is kSkipCompilation, the difference between the
+  // merge of the current profiles and the reference one is insignificant. In
+  // this case no file will be updated.
+  //
+  static ProcessingResult ProcessProfiles(
+      const std::vector<std::string>& profile_files,
+      const std::string& reference_profile_file);
+
+  static ProcessingResult ProcessProfiles(
+      const std::vector<int>& profile_files_fd_,
+      int reference_profile_file_fd);
+
+ private:
+  static ProcessingResult ProcessProfilesInternal(
+      const std::vector<ScopedFlock>& profile_files,
+      const ScopedFlock& reference_profile_file);
+
+  DISALLOW_COPY_AND_ASSIGN(ProfileAssistant);
+};
+
+}  // namespace art
+
+#endif  // ART_PROFMAN_PROFILE_ASSISTANT_H_
diff --git a/profman/profile_assistant_test.cc b/profman/profile_assistant_test.cc
new file mode 100644
index 0000000..543be5d
--- /dev/null
+++ b/profman/profile_assistant_test.cc
@@ -0,0 +1,255 @@
+/*
+ * 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 "profile_assistant.h"
+#include "jit/offline_profiling_info.h"
+#include "utils.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());
+  }
+
+  int GetFd(const ScratchFile& file) const {
+    return static_cast<int>(file.GetFd());
+  }
+
+  void CheckProfileInfo(ScratchFile& file, const ProfileCompilationInfo& info) {
+    ProfileCompilationInfo file_info;
+    ASSERT_TRUE(file.GetFile()->ResetOffset());
+    ASSERT_TRUE(file_info.Load(GetFd(file)));
+    ASSERT_TRUE(file_info.Equals(info));
+  }
+
+    // Runs test with given arguments.
+  int ProcessProfiles(const std::vector<int>& profiles_fd, int reference_profile_fd) {
+    std::string file_path = GetTestAndroidRoot();
+    if (IsHost()) {
+      file_path += "/bin/profman";
+    } else {
+      file_path += "/xbin/profman";
+    }
+    if (kIsDebugBuild) {
+      file_path += "d";
+    }
+
+    EXPECT_TRUE(OS::FileExists(file_path.c_str())) << file_path << " should be a valid file path";
+    std::vector<std::string> argv_str;
+    argv_str.push_back(file_path);
+    for (size_t k = 0; k < profiles_fd.size(); k++) {
+      argv_str.push_back("--profile-file-fd=" + std::to_string(profiles_fd[k]));
+    }
+    argv_str.push_back("--reference-profile-file-fd=" + std::to_string(reference_profile_fd));
+
+    std::string error;
+    return ExecAndReturnCode(argv_str, &error);
+  }
+};
+
+TEST_F(ProfileAssistantTest, AdviseCompilationEmptyReferences) {
+  ScratchFile profile1;
+  ScratchFile profile2;
+  ScratchFile reference_profile;
+
+  std::vector<int> profile_fds({
+      GetFd(profile1),
+      GetFd(profile2)});
+  int reference_profile_fd = GetFd(reference_profile);
+
+  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.
+  ASSERT_EQ(ProfileAssistant::kCompile,
+            ProcessProfiles(profile_fds, reference_profile_fd));
+  // The resulting compilation info must be equal to the merge of the inputs.
+  ProfileCompilationInfo result;
+  ASSERT_TRUE(reference_profile.GetFile()->ResetOffset());
+  ASSERT_TRUE(result.Load(reference_profile_fd));
+
+  ProfileCompilationInfo expected;
+  ASSERT_TRUE(expected.Load(info1));
+  ASSERT_TRUE(expected.Load(info2));
+  ASSERT_TRUE(expected.Equals(result));
+
+  // The information from profiles must remain the same.
+  CheckProfileInfo(profile1, info1);
+  CheckProfileInfo(profile2, info2);
+}
+
+TEST_F(ProfileAssistantTest, AdviseCompilationNonEmptyReferences) {
+  ScratchFile profile1;
+  ScratchFile profile2;
+  ScratchFile reference_profile;
+
+  std::vector<int> profile_fds({
+      GetFd(profile1),
+      GetFd(profile2)});
+  int reference_profile_fd = GetFd(reference_profile);
+
+  // 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_info;
+  SetupProfile("p1", 1, kNumberOfMethodsAlreadyCompiled, reference_profile,
+      &reference_info, kNumberOfMethodsToEnableCompilation / 2);
+
+  // We should advise compilation.
+  ASSERT_EQ(ProfileAssistant::kCompile,
+            ProcessProfiles(profile_fds, reference_profile_fd));
+
+  // The resulting compilation info must be equal to the merge of the inputs
+  ProfileCompilationInfo result;
+  ASSERT_TRUE(reference_profile.GetFile()->ResetOffset());
+  ASSERT_TRUE(result.Load(reference_profile_fd));
+
+  ProfileCompilationInfo expected;
+  ASSERT_TRUE(expected.Load(info1));
+  ASSERT_TRUE(expected.Load(info2));
+  ASSERT_TRUE(expected.Load(reference_info));
+  ASSERT_TRUE(expected.Equals(result));
+
+  // The information from profiles must remain the same.
+  CheckProfileInfo(profile1, info1);
+  CheckProfileInfo(profile2, info2);
+}
+
+TEST_F(ProfileAssistantTest, DoNotAdviseCompilation) {
+  ScratchFile profile1;
+  ScratchFile profile2;
+  ScratchFile reference_profile;
+
+  std::vector<int> profile_fds({
+      GetFd(profile1),
+      GetFd(profile2)});
+  int reference_profile_fd = GetFd(reference_profile);
+
+  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.
+  ASSERT_EQ(ProfileAssistant::kSkipCompilation,
+            ProcessProfiles(profile_fds, reference_profile_fd));
+
+  // 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_profile.GetFile()->GetLength());
+
+  // The information from profiles must remain the same.
+  CheckProfileInfo(profile1, info1);
+  CheckProfileInfo(profile2, info2);
+}
+
+TEST_F(ProfileAssistantTest, FailProcessingBecauseOfProfiles) {
+  ScratchFile profile1;
+  ScratchFile profile2;
+  ScratchFile reference_profile;
+
+  std::vector<int> profile_fds({
+      GetFd(profile1),
+      GetFd(profile2)});
+  int reference_profile_fd = 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 info2;
+  SetupProfile("p1", 2, kNumberOfMethodsToEnableCompilation, profile2, &info2);
+
+  // We should fail processing.
+  ASSERT_EQ(ProfileAssistant::kErrorBadProfiles,
+            ProcessProfiles(profile_fds, reference_profile_fd));
+
+  // The information from profiles must remain the same.
+  CheckProfileInfo(profile1, info1);
+  CheckProfileInfo(profile2, info2);
+
+  // Reference profile files must still remain empty.
+  ASSERT_EQ(0, reference_profile.GetFile()->GetLength());
+}
+
+TEST_F(ProfileAssistantTest, FailProcessingBecauseOfReferenceProfiles) {
+  ScratchFile profile1;
+  ScratchFile reference_profile;
+
+  std::vector<int> profile_fds({
+      GetFd(profile1)});
+  int reference_profile_fd = 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.
+  ASSERT_TRUE(profile1.GetFile()->ResetOffset());
+  ASSERT_TRUE(reference_profile.GetFile()->ResetOffset());
+  ASSERT_EQ(ProfileAssistant::kErrorBadProfiles,
+            ProcessProfiles(profile_fds, reference_profile_fd));
+
+  // The information from profiles must remain the same.
+  CheckProfileInfo(profile1, info1);
+}
+
+}  // namespace art
diff --git a/profman/profman.cc b/profman/profman.cc
new file mode 100644
index 0000000..7c9e449
--- /dev/null
+++ b/profman/profman.cc
@@ -0,0 +1,208 @@
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include "base/dumpable.h"
+#include "base/scoped_flock.h"
+#include "base/stringpiece.h"
+#include "base/stringprintf.h"
+#include "base/time_utils.h"
+#include "base/unix_file/fd_file.h"
+#include "jit/offline_profiling_info.h"
+#include "utils.h"
+#include "profile_assistant.h"
+
+namespace art {
+
+static int original_argc;
+static char** original_argv;
+
+static std::string CommandLine() {
+  std::vector<std::string> command;
+  for (int i = 0; i < original_argc; ++i) {
+    command.push_back(original_argv[i]);
+  }
+  return Join(command, ' ');
+}
+
+static void UsageErrorV(const char* fmt, va_list ap) {
+  std::string error;
+  StringAppendV(&error, fmt, ap);
+  LOG(ERROR) << error;
+}
+
+static void UsageError(const char* fmt, ...) {
+  va_list ap;
+  va_start(ap, fmt);
+  UsageErrorV(fmt, ap);
+  va_end(ap);
+}
+
+NO_RETURN static void Usage(const char *fmt, ...) {
+  va_list ap;
+  va_start(ap, fmt);
+  UsageErrorV(fmt, ap);
+  va_end(ap);
+
+  UsageError("Command: %s", CommandLine().c_str());
+  UsageError("Usage: profman [options]...");
+  UsageError("");
+  UsageError("  --profile-file=<filename>: specify profiler output file to use for compilation.");
+  UsageError("      Can be specified multiple time, in which case the data from the different");
+  UsageError("      profiles will be aggregated.");
+  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=<filename>: specify a reference profile.");
+  UsageError("      The data in this file will be compared with the data obtained by merging");
+  UsageError("      all the files specified with --profile-file or --profile-file-fd.");
+  UsageError("      If the exit code is EXIT_COMPILE then all --profile-file will be merged into");
+  UsageError("      --reference-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("");
+
+  exit(EXIT_FAILURE);
+}
+
+class ProfMan FINAL {
+ public:
+  ProfMan() :
+      reference_profile_file_fd_(-1),
+      start_ns_(NanoTime()) {}
+
+  ~ProfMan() {
+    LogCompletionTime();
+  }
+
+  void ParseArgs(int argc, char **argv) {
+    original_argc = argc;
+    original_argv = argv;
+
+    InitLogging(argv);
+
+    // Skip over the command name.
+    argv++;
+    argc--;
+
+    if (argc == 0) {
+      Usage("No arguments specified");
+    }
+
+    for (int i = 0; i < argc; ++i) {
+      const StringPiece option(argv[i]);
+      const bool log_options = false;
+      if (log_options) {
+        LOG(INFO) << "patchoat: option[" << i << "]=" << argv[i];
+      }
+      if (option.starts_with("--profile-file=")) {
+        profile_files_.push_back(option.substr(strlen("--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=")) {
+        reference_profile_file_ = option.substr(strlen("--reference-profile-file=")).ToString();
+      } else if (option.starts_with("--reference-profile-file-fd=")) {
+        ParseUintOption(option, "--reference-profile-file-fd", &reference_profile_file_fd_, Usage);
+      } else {
+        Usage("Unknown argument %s", option.data());
+      }
+    }
+
+    if (profile_files_.empty() && profile_files_fd_.empty()) {
+      Usage("No profile files specified.");
+    }
+    if (!profile_files_.empty() && !profile_files_fd_.empty()) {
+      Usage("Profile files should not be specified with both --profile-file-fd and --profile-file");
+    }
+    if (!reference_profile_file_.empty() && (reference_profile_file_fd_ != -1)) {
+      Usage("--reference-profile-file-fd should only be supplied with --profile-file-fd");
+    }
+    if (reference_profile_file_.empty() && (reference_profile_file_fd_ == -1)) {
+      Usage("Reference profile file not specified");
+    }
+  }
+
+  ProfileAssistant::ProcessingResult ProcessProfiles() {
+    ProfileAssistant::ProcessingResult result;
+    if (profile_files_.empty()) {
+      // The file doesn't need to be flushed here (ProcessProfiles will do it)
+      // so don't check the usage.
+      File file(reference_profile_file_fd_, false);
+      result = ProfileAssistant::ProcessProfiles(profile_files_fd_, reference_profile_file_fd_);
+      CloseAllFds(profile_files_fd_, "profile_files_fd_");
+    } else {
+      result = ProfileAssistant::ProcessProfiles(profile_files_, reference_profile_file_);
+    }
+    return result;
+  }
+
+ private:
+  static void ParseFdForCollection(const StringPiece& option,
+                                   const char* arg_name,
+                                   std::vector<int>* fds) {
+    int fd;
+    ParseUintOption(option, arg_name, &fd, Usage);
+    fds->push_back(fd);
+  }
+
+  static void CloseAllFds(const std::vector<int>& 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;
+      }
+    }
+  }
+
+  void LogCompletionTime() {
+    LOG(INFO) << "profman took " << PrettyDuration(NanoTime() - start_ns_);
+  }
+
+  std::vector<std::string> profile_files_;
+  std::vector<int> profile_files_fd_;
+  std::string reference_profile_file_;
+  int reference_profile_file_fd_;
+  uint64_t start_ns_;
+};
+
+// See ProfileAssistant::ProcessingResult for return codes.
+static int profman(int argc, char** argv) {
+  ProfMan profman;
+
+  // Parse arguments. Argument mistakes will lead to exit(EXIT_FAILURE) in UsageError.
+  profman.ParseArgs(argc, argv);
+
+  // Process profile information and assess if we need to do a profile guided compilation.
+  // This operation involves I/O.
+  return profman.ProcessProfiles();
+}
+
+}  // namespace art
+
+int main(int argc, char **argv) {
+  return art::profman(argc, argv);
+}
+
diff --git a/runtime/base/scoped_flock.cc b/runtime/base/scoped_flock.cc
index 814cbd0..0e8031f 100644
--- a/runtime/base/scoped_flock.cc
+++ b/runtime/base/scoped_flock.cc
@@ -83,7 +83,7 @@
 }
 
 bool ScopedFlock::Init(File* file, std::string* error_msg) {
-  file_.reset(new File(dup(file->Fd()), true));
+  file_.reset(new File(dup(file->Fd()), file->GetPath(), file->CheckUsage(), file->ReadOnlyMode()));
   if (file_->Fd() == -1) {
     file_.reset();
     *error_msg = StringPrintf("Failed to duplicate open file '%s': %s",
@@ -114,7 +114,13 @@
   if (file_.get() != nullptr) {
     int flock_result = TEMP_FAILURE_RETRY(flock(file_->Fd(), LOCK_UN));
     CHECK_EQ(0, flock_result);
-    if (file_->FlushCloseOrErase() != 0) {
+    int close_result = -1;
+    if (file_->ReadOnlyMode()) {
+      close_result = file_->Close();
+    } else {
+      close_result = file_->FlushCloseOrErase();
+    }
+    if (close_result != 0) {
       PLOG(WARNING) << "Could not close scoped file lock file.";
     }
   }
diff --git a/runtime/base/unix_file/fd_file.cc b/runtime/base/unix_file/fd_file.cc
index e17bebb..4672948 100644
--- a/runtime/base/unix_file/fd_file.cc
+++ b/runtime/base/unix_file/fd_file.cc
@@ -35,18 +35,22 @@
 
 namespace unix_file {
 
-FdFile::FdFile() : guard_state_(GuardState::kClosed), fd_(-1), auto_close_(true) {
+FdFile::FdFile()
+    : guard_state_(GuardState::kClosed), fd_(-1), auto_close_(true), read_only_mode_(false) {
 }
 
 FdFile::FdFile(int fd, bool check_usage)
     : guard_state_(check_usage ? GuardState::kBase : GuardState::kNoCheck),
-      fd_(fd), auto_close_(true) {
+      fd_(fd), auto_close_(true), read_only_mode_(false) {
 }
 
 FdFile::FdFile(int fd, const std::string& path, bool check_usage)
+    : FdFile(fd, path, check_usage, false) {
+}
+
+FdFile::FdFile(int fd, const std::string& path, bool check_usage, bool read_only_mode)
     : guard_state_(check_usage ? GuardState::kBase : GuardState::kNoCheck),
-      fd_(fd), file_path_(path), auto_close_(true) {
-  CHECK_NE(0U, path.size());
+      fd_(fd), file_path_(path), auto_close_(true), read_only_mode_(read_only_mode) {
 }
 
 FdFile::~FdFile() {
@@ -99,6 +103,7 @@
 
 bool FdFile::Open(const std::string& path, int flags, mode_t mode) {
   CHECK_EQ(fd_, -1) << path;
+  read_only_mode_ = (flags & O_RDONLY) != 0;
   fd_ = TEMP_FAILURE_RETRY(open(path.c_str(), flags, mode));
   if (fd_ == -1) {
     return false;
@@ -136,6 +141,7 @@
 }
 
 int FdFile::Flush() {
+  DCHECK(!read_only_mode_);
 #ifdef __linux__
   int rc = TEMP_FAILURE_RETRY(fdatasync(fd_));
 #else
@@ -155,6 +161,7 @@
 }
 
 int FdFile::SetLength(int64_t new_length) {
+  DCHECK(!read_only_mode_);
 #ifdef __linux__
   int rc = TEMP_FAILURE_RETRY(ftruncate64(fd_, new_length));
 #else
@@ -171,6 +178,7 @@
 }
 
 int64_t FdFile::Write(const char* buf, int64_t byte_count, int64_t offset) {
+  DCHECK(!read_only_mode_);
 #ifdef __linux__
   int rc = TEMP_FAILURE_RETRY(pwrite64(fd_, buf, byte_count, offset));
 #else
@@ -184,6 +192,14 @@
   return fd_;
 }
 
+bool FdFile::ReadOnlyMode() const {
+  return read_only_mode_;
+}
+
+bool FdFile::CheckUsage() const {
+  return guard_state_ != GuardState::kNoCheck;
+}
+
 bool FdFile::IsOpened() const {
   return fd_ >= 0;
 }
@@ -219,6 +235,7 @@
 }
 
 bool FdFile::WriteFully(const void* buffer, size_t byte_count) {
+  DCHECK(!read_only_mode_);
   const char* ptr = static_cast<const char*>(buffer);
   moveTo(GuardState::kBase, GuardState::kClosed, "Writing into closed file.");
   while (byte_count > 0) {
@@ -233,6 +250,7 @@
 }
 
 bool FdFile::Copy(FdFile* input_file, int64_t offset, int64_t size) {
+  DCHECK(!read_only_mode_);
   off_t off = static_cast<off_t>(offset);
   off_t sz = static_cast<off_t>(size);
   if (offset < 0 || static_cast<int64_t>(off) != offset ||
@@ -279,12 +297,14 @@
 }
 
 void FdFile::Erase() {
+  DCHECK(!read_only_mode_);
   TEMP_FAILURE_RETRY(SetLength(0));
   TEMP_FAILURE_RETRY(Flush());
   TEMP_FAILURE_RETRY(Close());
 }
 
 int FdFile::FlushCloseOrErase() {
+  DCHECK(!read_only_mode_);
   int flush_result = TEMP_FAILURE_RETRY(Flush());
   if (flush_result != 0) {
     LOG(::art::ERROR) << "CloseOrErase failed while flushing a file.";
@@ -301,6 +321,7 @@
 }
 
 int FdFile::FlushClose() {
+  DCHECK(!read_only_mode_);
   int flush_result = TEMP_FAILURE_RETRY(Flush());
   if (flush_result != 0) {
     LOG(::art::ERROR) << "FlushClose failed while flushing a file.";
@@ -317,6 +338,7 @@
 }
 
 bool FdFile::ClearContent() {
+  DCHECK(!read_only_mode_);
   if (SetLength(0) < 0) {
     PLOG(art::ERROR) << "Failed to reset the length";
     return false;
@@ -325,6 +347,7 @@
 }
 
 bool FdFile::ResetOffset() {
+  DCHECK(!read_only_mode_);
   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";
diff --git a/runtime/base/unix_file/fd_file.h b/runtime/base/unix_file/fd_file.h
index 1e2d8af..8040afe 100644
--- a/runtime/base/unix_file/fd_file.h
+++ b/runtime/base/unix_file/fd_file.h
@@ -37,6 +37,7 @@
   // file descriptor. (Use DisableAutoClose to retain ownership.)
   FdFile(int fd, bool checkUsage);
   FdFile(int fd, const std::string& path, bool checkUsage);
+  FdFile(int fd, const std::string& path, bool checkUsage, bool read_only_mode);
 
   // Destroys an FdFile, closing the file descriptor if Close hasn't already
   // been called. (If you care about the return value of Close, call it
@@ -68,6 +69,8 @@
 
   // Bonus API.
   int Fd() const;
+  bool ReadOnlyMode() const;
+  bool CheckUsage() const;
   bool IsOpened() const;
   const std::string& GetPath() const {
     return file_path_;
@@ -119,6 +122,7 @@
   int fd_;
   std::string file_path_;
   bool auto_close_;
+  bool read_only_mode_;
 
   DISALLOW_COPY_AND_ASSIGN(FdFile);
 };
diff --git a/runtime/jit/offline_profiling_info.cc b/runtime/jit/offline_profiling_info.cc
index 0aff1f7..747b112 100644
--- a/runtime/jit/offline_profiling_info.cc
+++ b/runtime/jit/offline_profiling_info.cc
@@ -125,8 +125,8 @@
  *    app.apk,131232145,11,23,454,54
  *    app.apk:classes5.dex,218490184,39,13,49,1
  **/
-bool ProfileCompilationInfo::Save(uint32_t fd) {
-  DCHECK_GE(fd, 0u);
+bool ProfileCompilationInfo::Save(int fd) {
+  DCHECK_GE(fd, 0);
   // TODO(calin): Profile this and see how much memory it takes. If too much,
   // write to file directly.
   std::ostringstream os;
@@ -232,8 +232,8 @@
   return new_line_pos == -1 ? new_line_pos : new_line_pos + 1;
 }
 
-bool ProfileCompilationInfo::Load(uint32_t fd) {
-  DCHECK_GE(fd, 0u);
+bool ProfileCompilationInfo::Load(int fd) {
+  DCHECK_GE(fd, 0);
 
   std::string current_line;
   const int kBufferSize = 1024;
@@ -343,7 +343,7 @@
   return os.str();
 }
 
-bool ProfileCompilationInfo::Equals(ProfileCompilationInfo& other) {
+bool ProfileCompilationInfo::Equals(const ProfileCompilationInfo& other) {
   return info_.Equals(other.info_);
 }
 
diff --git a/runtime/jit/offline_profiling_info.h b/runtime/jit/offline_profiling_info.h
index c388c4a..edc591c 100644
--- a/runtime/jit/offline_profiling_info.h
+++ b/runtime/jit/offline_profiling_info.h
@@ -46,11 +46,11 @@
                                 const std::vector<ArtMethod*>& methods);
 
   // Loads profile information from the given file descriptor.
-  bool Load(uint32_t fd);
+  bool Load(int fd);
   // Loads the data from another ProfileCompilationInfo object.
   bool Load(const ProfileCompilationInfo& info);
   // Saves the profile data to the given file descriptor.
-  bool Save(uint32_t fd);
+  bool Save(int fd);
   // Returns the number of methods that were profiled.
   uint32_t GetNumberOfMethods() const;
 
@@ -65,8 +65,7 @@
                        bool print_full_dex_location = true) const;
 
   // For testing purposes.
-  bool Equals(ProfileCompilationInfo& other);
-  // Exposed for testing purpose.
+  bool Equals(const ProfileCompilationInfo& other);
   static std::string GetProfileDexFileKey(const std::string& dex_location);
 
  private:
diff --git a/runtime/utils.cc b/runtime/utils.cc
index 07f94c0..13564a6 100644
--- a/runtime/utils.cc
+++ b/runtime/utils.cc
@@ -1392,9 +1392,8 @@
   return filename;
 }
 
-bool Exec(std::vector<std::string>& arg_vector, std::string* error_msg) {
+int ExecAndReturnCode(std::vector<std::string>& arg_vector, std::string* error_msg) {
   const std::string command_line(Join(arg_vector, ' '));
-
   CHECK_GE(arg_vector.size(), 1U) << command_line;
 
   // Convert the args to char pointers.
@@ -1417,7 +1416,6 @@
     setpgid(0, 0);
 
     execv(program, &args[0]);
-
     PLOG(ERROR) << "Failed to execv(" << command_line << ")";
     // _exit to avoid atexit handlers in child.
     _exit(1);
@@ -1425,23 +1423,32 @@
     if (pid == -1) {
       *error_msg = StringPrintf("Failed to execv(%s) because fork failed: %s",
                                 command_line.c_str(), strerror(errno));
-      return false;
+      return -1;
     }
 
     // wait for subprocess to finish
-    int status;
+    int status = -1;
     pid_t got_pid = TEMP_FAILURE_RETRY(waitpid(pid, &status, 0));
     if (got_pid != pid) {
       *error_msg = StringPrintf("Failed after fork for execv(%s) because waitpid failed: "
                                 "wanted %d, got %d: %s",
                                 command_line.c_str(), pid, got_pid, strerror(errno));
-      return false;
+      return -1;
     }
-    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
-      *error_msg = StringPrintf("Failed execv(%s) because non-0 exit status",
-                                command_line.c_str());
-      return false;
+    if (WIFEXITED(status)) {
+      return WEXITSTATUS(status);
     }
+    return -1;
+  }
+}
+
+bool Exec(std::vector<std::string>& arg_vector, std::string* error_msg) {
+  int status = ExecAndReturnCode(arg_vector, error_msg);
+  if (status != 0) {
+    const std::string command_line(Join(arg_vector, ' '));
+    *error_msg = StringPrintf("Failed execv(%s) because non-0 exit status",
+                              command_line.c_str());
+    return false;
   }
   return true;
 }
diff --git a/runtime/utils.h b/runtime/utils.h
index 79e4da1..83ac0b87 100644
--- a/runtime/utils.h
+++ b/runtime/utils.h
@@ -292,6 +292,7 @@
 
 // Wrapper on fork/execv to run a command in a subprocess.
 bool Exec(std::vector<std::string>& arg_vector, std::string* error_msg);
+int ExecAndReturnCode(std::vector<std::string>& arg_vector, std::string* error_msg);
 
 // Returns true if the file exists.
 bool FileExists(const std::string& filename);
@@ -348,7 +349,7 @@
                             UsageFn Usage,
                             bool is_long_option = true) {
   std::string option_prefix = option_name + (is_long_option ? "=" : "");
-  DCHECK(option.starts_with(option_prefix));
+  DCHECK(option.starts_with(option_prefix)) << option << " " << option_prefix;
   const char* value_string = option.substr(option_prefix.size()).data();
   int64_t parsed_integer_value = 0;
   if (!ParseInt(value_string, &parsed_integer_value)) {