Revert "Revert "Add profman tool: responsible to process profiles""

This reverts commit 3da74687e42de7d33a8e75df9bd64374e650f75e.

Change-Id: Id005096bd8063c6c602744d4476d5eb7e0d34e90
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);
+}
+