dex2oat: add --cpu-set command-line option

Enables affinitizing dex2oat threads to a specific group of CPUs.

Bug: 141446571
Bug: 149395059
Test: art/test/run-test -Xcompiler-option --cpu-set=0,1 956
Test: art/test/run-test -Xcompiler-option --cpu-set=,0,1 956
Test: art/test/run-test -Xcompiler-option --cpu-set=,, 956
Test: art/test/run-test -Xcompiler-option --cpu-set=0,a 956
Test: cmdline_parser_test
Change-Id: I4bb1519beacd329da1a69af31982a6154d315865
Merged-In: I4bb1519beacd329da1a69af31982a6154d315865
(cherry picked from commit ffc791c748dd28cc6bc7fff825b3c1b7af96abb3)
diff --git a/cmdline/cmdline_parser_test.cc b/cmdline/cmdline_parser_test.cc
index 101e5c4..a70b34d 100644
--- a/cmdline/cmdline_parser_test.cc
+++ b/cmdline/cmdline_parser_test.cc
@@ -20,7 +20,6 @@
 
 #include "gtest/gtest.h"
 
-#include "base/mutex.h"
 #include "base/utils.h"
 #include "jdwp_provider.h"
 #include "experimental_flags.h"
@@ -578,4 +577,21 @@
   EXPECT_KEY_VALUE(map, M::MethodTrace, Unit{});
   EXPECT_KEY_VALUE(map, M::LargeObjectSpace, gc::space::LargeObjectSpaceType::kMap);
 }  //  TEST_F
+
+TEST_F(CmdlineParserTest, TypesNotInRuntime) {
+  CmdlineType<std::vector<int32_t>> ct;
+  auto success0 =
+      CmdlineParseResult<std::vector<int32_t>>::Success(std::vector<int32_t>({1, 2, 3, 4}));
+  EXPECT_EQ(success0, ct.Parse("1,2,3,4"));
+  auto success1 = CmdlineParseResult<std::vector<int32_t>>::Success(std::vector<int32_t>({0}));
+  EXPECT_EQ(success1, ct.Parse("1"));
+
+  EXPECT_FALSE(ct.Parse("").IsSuccess());
+  EXPECT_FALSE(ct.Parse(",").IsSuccess());
+  EXPECT_FALSE(ct.Parse("1,").IsSuccess());
+  EXPECT_FALSE(ct.Parse(",1").IsSuccess());
+  EXPECT_FALSE(ct.Parse("1a2").IsSuccess());
+  EXPECT_EQ(CmdlineResult::kOutOfRange, ct.Parse("1,10000000000000").GetStatus());
+  EXPECT_EQ(CmdlineResult::kOutOfRange, ct.Parse("-10000000000000,123").GetStatus());
+}  // TEST_F
 }  // namespace art
diff --git a/cmdline/cmdline_types.h b/cmdline/cmdline_types.h
index dd9221d..6f784b3 100644
--- a/cmdline/cmdline_types.h
+++ b/cmdline/cmdline_types.h
@@ -405,6 +405,39 @@
   static const char* Name() { return "ParseStringList<Separator>"; }
 };
 
+template <>
+struct CmdlineType<std::vector<int32_t>> : CmdlineTypeParser<std::vector<int32_t>> {
+  using Result = CmdlineParseResult<std::vector<int32_t>>;
+
+  Result Parse(const std::string& args) {
+    std::vector<int32_t> list;
+    const char* pos = args.c_str();
+    errno = 0;
+
+    while (true) {
+      char* end = nullptr;
+      int64_t value = strtol(pos, &end, 10);
+      if (pos == end ||  errno == EINVAL) {
+        return Result::Failure("Failed to parse integer from " + args);
+      } else if ((errno == ERANGE) ||  // NOLINT [runtime/int] [4]
+                 value < std::numeric_limits<int32_t>::min() ||
+                 value > std::numeric_limits<int32_t>::max()) {
+        return Result::OutOfRange("Failed to parse integer from " + args + "; out of range");
+      }
+      list.push_back(static_cast<int32_t>(value));
+      if (*end == '\0') {
+        break;
+      } else if (*end != ',') {
+        return Result::Failure(std::string("Unexpected character: ") + *end);
+      }
+      pos = end + 1;
+    }
+    return Result::Success(std::move(list));
+  }
+
+  static const char* Name() { return "std::vector<int32_t>"; }
+};
+
 static gc::CollectorType ParseCollectorType(const std::string& option) {
   if (option == "MS" || option == "nonconcurrent") {
     return gc::kCollectorTypeMS;
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index 278523e..9961608 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -29,11 +29,15 @@
 #include <type_traits>
 #include <vector>
 
-#if defined(__linux__) && defined(__arm__)
+#if defined(__linux__)
+#include <sched.h>
+#if defined(__arm__)
 #include <sys/personality.h>
 #include <sys/utsname.h>
+#endif  // __arm__
 #endif
 
+#include "android-base/parseint.h"
 #include "android-base/stringprintf.h"
 #include "android-base/strings.h"
 
@@ -222,6 +226,10 @@
   UsageError("       host system.");
   UsageError("      Example: -j12");
   UsageError("");
+  UsageError("  --cpu-set=<set>: sets the cpu affinity to <set>. The <set> argument is a comma");
+  UsageError("    separated list of CPUs.");
+  UsageError("    Example: --cpu-set=0,1,2,3");
+  UsageError("");
   UsageError("  --dex-file=<dex-file>: specifies a .dex, .jar, or .apk file to compile.");
   UsageError("      Example: --dex-file=/system/framework/core.jar");
   UsageError("");
@@ -500,6 +508,36 @@
   exit(EXIT_FAILURE);
 }
 
+
+// Set CPU affinity from a string containing a comma-separated list of numeric CPU identifiers.
+static void SetCpuAffinity(const std::vector<int32_t>& cpu_list) {
+#ifdef __linux__
+  int cpu_count = sysconf(_SC_NPROCESSORS_CONF);
+  cpu_set_t target_cpu_set;
+  CPU_ZERO(&target_cpu_set);
+
+  for (int32_t cpu : cpu_list) {
+    if (cpu >= 0 && cpu < cpu_count) {
+      CPU_SET(cpu, &target_cpu_set);
+    } else {
+      // Argument error is considered fatal, suggests misconfigured system properties.
+      Usage("Invalid cpu \"d\" specified in --cpu-set argument (nprocessors = %d)",
+            cpu, cpu_count);
+    }
+  }
+
+  if (sched_setaffinity(getpid(), sizeof(target_cpu_set), &target_cpu_set) == -1) {
+    // Failure to set affinity may be outside control of requestor, log warning rather than
+    // treating as fatal.
+    PLOG(WARNING) << "Failed to set CPU affinity.";
+  }
+#else
+  LOG(WARNING) << "--cpu-set not supported on this platform.";
+#endif  // __linux__
+}
+
+
+
 // The primary goal of the watchdog is to prevent stuck build servers
 // during development when fatal aborts lead to a cascade of failures
 // that result in a deadlock.
@@ -929,6 +967,10 @@
       }
     }
 
+    if (!cpu_set_.empty()) {
+      SetCpuAffinity(cpu_set_);
+    }
+
     if (compiler_options_->inline_max_code_units_ == CompilerOptions::kUnsetInlineMaxCodeUnits) {
       compiler_options_->inline_max_code_units_ = CompilerOptions::kDefaultInlineMaxCodeUnits;
     }
@@ -1136,6 +1178,7 @@
     AssignIfExists(args, M::Threads, &thread_count_);
     AssignIfExists(args, M::ImageClasses, &image_classes_filename_);
     AssignIfExists(args, M::ImageClassesZip, &image_classes_zip_filename_);
+    AssignIfExists(args, M::CpuSet, &cpu_set_);
     AssignIfExists(args, M::Passes, &passes_to_run_filename_);
     AssignIfExists(args, M::BootImage, &parser_options->boot_image_filename);
     AssignIfExists(args, M::AndroidRoot, &android_root_);
@@ -2743,6 +2786,7 @@
   std::unique_ptr<ClassLoaderContext> stored_class_loader_context_;
 
   size_t thread_count_;
+  std::vector<int32_t> cpu_set_;
   uint64_t start_ns_;
   uint64_t start_cputime_ns_;
   std::unique_ptr<WatchDog> watchdog_;
diff --git a/dex2oat/dex2oat_options.cc b/dex2oat/dex2oat_options.cc
index 4a19fb1..80c9a16 100644
--- a/dex2oat/dex2oat_options.cc
+++ b/dex2oat/dex2oat_options.cc
@@ -206,6 +206,9 @@
       .Define("-j_")
           .WithType<unsigned int>()
           .IntoKey(M::Threads)
+      .Define("--cpu-set=_")
+          .WithType<std::vector<int32_t>>()
+          .IntoKey(M::CpuSet)
       .Define("--android-root=_")
           .WithType<std::string>()
           .IntoKey(M::AndroidRoot)
diff --git a/dex2oat/dex2oat_options.def b/dex2oat/dex2oat_options.def
index 0717e1a..8201c3c 100644
--- a/dex2oat/dex2oat_options.def
+++ b/dex2oat/dex2oat_options.def
@@ -53,6 +53,7 @@
 DEX2OAT_OPTIONS_KEY (bool,                           Watchdog)
 DEX2OAT_OPTIONS_KEY (int,                            WatchdogTimeout)
 DEX2OAT_OPTIONS_KEY (unsigned int,                   Threads)
+DEX2OAT_OPTIONS_KEY (std::vector<std::int32_t>,      CpuSet)
 DEX2OAT_OPTIONS_KEY (std::vector<std::string>,       ImageFilenames)
 DEX2OAT_OPTIONS_KEY (std::string,                    ImageClasses)
 DEX2OAT_OPTIONS_KEY (std::string,                    ImageClassesZip)