Return dex2oat wall time and CPU time in results.

Bug: 245380798
Test: m test-art-host-gtest-art_artd_tests
Test: atest ArtServiceTests
Test: adb shell pm art optimize-package -m speed -f \
  com.google.android.youtube
Ignore-AOSP-First: ART Services.
Change-Id: I08881e97ba51f44613eb7a31a59ba1e45ea78ee0
diff --git a/artd/artd.cc b/artd/artd.cc
index 36f7111..4cadee0 100644
--- a/artd/artd.cc
+++ b/artd/artd.cc
@@ -53,6 +53,7 @@
 #include "base/file_utils.h"
 #include "base/globals.h"
 #include "base/os.h"
+#include "exec_utils.h"
 #include "file_utils.h"
 #include "fmt/format.h"
 #include "oat_file_assistant.h"
@@ -70,6 +71,7 @@
 
 using ::aidl::com::android::server::art::ArtifactsPath;
 using ::aidl::com::android::server::art::DexoptOptions;
+using ::aidl::com::android::server::art::DexoptResult;
 using ::aidl::com::android::server::art::DexoptTrigger;
 using ::aidl::com::android::server::art::FileVisibility;
 using ::aidl::com::android::server::art::FsPermission;
@@ -566,7 +568,9 @@
                                 const std::optional<VdexPath>& in_inputVdex,
                                 PriorityClass in_priorityClass,
                                 const DexoptOptions& in_dexoptOptions,
-                                bool* _aidl_return) {
+                                DexoptResult* _aidl_return) {
+  _aidl_return->cancelled = false;
+
   std::string oat_path = OR_RETURN_FATAL(BuildOatPath(in_outputArtifacts.artifactsPath));
   std::string vdex_path = OatPathToVdexPath(oat_path);
   std::string art_path = OatPathToArtPath(oat_path);
@@ -673,9 +677,12 @@
   LOG(INFO) << "Running dex2oat: " << Join(args.Get(), /*separator=*/" ")
             << "\nOpened FDs: " << fd_logger;
 
-  Result<int> result = ExecAndReturnCode(args.Get(), kLongTimeoutSec);
+  ProcessStat stat;
+  Result<int> result = ExecAndReturnCode(args.Get(), kLongTimeoutSec, &stat);
+  _aidl_return->wallTimeMs = stat.wall_time_ms;
+  _aidl_return->cpuTimeMs = stat.cpu_time_ms;
   if (!result.ok()) {
-    // TODO(b/244412198): Return false if dexopt is cancelled upon request.
+    // TODO(b/244412198): Return cancelled=true if dexopt is cancelled upon request.
     return NonFatal("Failed to run dex2oat: " + result.error().message());
   }
   if (result.value() != 0) {
@@ -684,7 +691,6 @@
 
   NewFile::CommitAllOrAbandon(files_to_commit, files_to_delete);
 
-  *_aidl_return = true;
   return ScopedAStatus::ok();
 }
 
@@ -863,10 +869,12 @@
 }
 
 android::base::Result<int> Artd::ExecAndReturnCode(const std::vector<std::string>& args,
-                                                   int timeout_sec) const {
+                                                   int timeout_sec,
+                                                   ProcessStat* stat) const {
   bool ignored_timed_out = false;  // This information is encoded in the error message.
   std::string error_msg;
-  int exit_code = exec_utils_->ExecAndReturnCode(args, timeout_sec, &ignored_timed_out, &error_msg);
+  int exit_code =
+      exec_utils_->ExecAndReturnCode(args, timeout_sec, &ignored_timed_out, stat, &error_msg);
   if (exit_code < 0) {
     return Error() << error_msg;
   }
diff --git a/artd/artd.h b/artd/artd.h
index d637a0b..931f606 100644
--- a/artd/artd.h
+++ b/artd/artd.h
@@ -100,7 +100,7 @@
       const std::optional<aidl::com::android::server::art::VdexPath>& in_inputVdex,
       aidl::com::android::server::art::PriorityClass in_priorityClass,
       const aidl::com::android::server::art::DexoptOptions& in_dexoptOptions,
-      bool* _aidl_return) override;
+      aidl::com::android::server::art::DexoptResult* _aidl_return) override;
 
   android::base::Result<void> Start();
 
@@ -117,7 +117,8 @@
   bool DenyArtApexDataFiles();
 
   android::base::Result<int> ExecAndReturnCode(const std::vector<std::string>& arg_vector,
-                                               int timeout_sec) const;
+                                               int timeout_sec,
+                                               ProcessStat* stat = nullptr) const;
 
   android::base::Result<std::string> GetProfman();
 
diff --git a/artd/artd_test.cc b/artd/artd_test.cc
index fcc15d4..c03b245 100644
--- a/artd/artd_test.cc
+++ b/artd/artd_test.cc
@@ -51,6 +51,7 @@
 using ::aidl::com::android::server::art::ArtifactsPath;
 using ::aidl::com::android::server::art::DexMetadataPath;
 using ::aidl::com::android::server::art::DexoptOptions;
+using ::aidl::com::android::server::art::DexoptResult;
 using ::aidl::com::android::server::art::FileVisibility;
 using ::aidl::com::android::server::art::FsPermission;
 using ::aidl::com::android::server::art::OutputArtifacts;
@@ -73,12 +74,15 @@
 using ::testing::ContainsRegex;
 using ::testing::DoAll;
 using ::testing::ElementsAre;
+using ::testing::Field;
 using ::testing::HasSubstr;
 using ::testing::IsEmpty;
+using ::testing::Matcher;
 using ::testing::MockFunction;
 using ::testing::Not;
 using ::testing::ResultOf;
 using ::testing::Return;
+using ::testing::SetArgPointee;
 using ::testing::WithArg;
 
 using RefProfilePath = ProfilePath::RefProfilePath;
@@ -171,11 +175,15 @@
   int ExecAndReturnCode(const std::vector<std::string>& arg_vector,
                         int,
                         bool*,
+                        ProcessStat* stat,
                         std::string*) const override {
-    return DoExecAndReturnCode(arg_vector);
+    return DoExecAndReturnCode(arg_vector, stat);
   }
 
-  MOCK_METHOD(int, DoExecAndReturnCode, (const std::vector<std::string>& arg_vector), (const));
+  MOCK_METHOD(int,
+              DoExecAndReturnCode,
+              (const std::vector<std::string>& arg_vector, ProcessStat* stat),
+              (const));
 };
 
 class ArtdTest : public CommonArtTest {
@@ -246,9 +254,11 @@
     CommonArtTest::TearDown();
   }
 
-  void RunDexopt(binder_exception_t expected_status = EX_NONE, bool expected_aidl_return = true) {
+  void RunDexopt(binder_exception_t expected_status = EX_NONE,
+                 Matcher<DexoptResult> aidl_return_matcher = Field(&DexoptResult::cancelled,
+                                                                   false)) {
     InitDexoptInputFiles();
-    bool aidl_return;
+    DexoptResult aidl_return;
     ndk::ScopedAStatus status = artd_->dexopt(output_artifacts_,
                                               dex_file_,
                                               isa_,
@@ -261,7 +271,7 @@
                                               &aidl_return);
     ASSERT_EQ(status.getExceptionCode(), expected_status) << status.getMessage();
     if (status.isOk()) {
-      ASSERT_EQ(aidl_return, expected_aidl_return);
+      ASSERT_THAT(aidl_return, std::move(aidl_return_matcher));
     }
   }
 
@@ -410,38 +420,47 @@
 }
 
 TEST_F(ArtdTest, dexopt) {
-  EXPECT_CALL(*mock_exec_utils_,
-              DoExecAndReturnCode(WhenSplitBy(
-                  "--",
-                  AllOf(Contains(art_root_ + "/bin/art_exec"), Contains("--drop-capabilities")),
-                  AllOf(Contains(art_root_ + "/bin/dex2oat32"),
-                        Contains(Flag("--zip-fd=", FdOf(dex_file_))),
-                        Contains(Flag("--zip-location=", dex_file_)),
-                        Contains(Flag("--oat-location=", scratch_path_ + "/a/oat/arm64/b.odex")),
-                        Contains(Flag("--instruction-set=", "arm64")),
-                        Contains(Flag("--compiler-filter=", "speed")),
-                        Contains(Flag(
-                            "--profile-file-fd=",
-                            FdOf(android_data_ +
-                                 "/misc/profiles/ref/com.android.foo/primary.prof.12345.tmp")))))))
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(
+          WhenSplitBy(
+              "--",
+              AllOf(Contains(art_root_ + "/bin/art_exec"), Contains("--drop-capabilities")),
+              AllOf(Contains(art_root_ + "/bin/dex2oat32"),
+                    Contains(Flag("--zip-fd=", FdOf(dex_file_))),
+                    Contains(Flag("--zip-location=", dex_file_)),
+                    Contains(Flag("--oat-location=", scratch_path_ + "/a/oat/arm64/b.odex")),
+                    Contains(Flag("--instruction-set=", "arm64")),
+                    Contains(Flag("--compiler-filter=", "speed")),
+                    Contains(
+                        Flag("--profile-file-fd=",
+                             FdOf(android_data_ +
+                                  "/misc/profiles/ref/com.android.foo/primary.prof.12345.tmp"))))),
+          _))
       .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--oat-fd=", "oat")),
                       WithArg<0>(WriteToFdFlag("--output-vdex-fd=", "vdex")),
+                      SetArgPointee<1>(ProcessStat{.wall_time_ms = 100, .cpu_time_ms = 400}),
                       Return(0)));
-  RunDexopt();
+  RunDexopt(EX_NONE,
+            AllOf(Field(&DexoptResult::cancelled, false),
+                  Field(&DexoptResult::wallTimeMs, 100),
+                  Field(&DexoptResult::cpuTimeMs, 400)));
 
   CheckContent(scratch_path_ + "/a/oat/arm64/b.odex", "oat");
   CheckContent(scratch_path_ + "/a/oat/arm64/b.vdex", "vdex");
 }
 
 TEST_F(ArtdTest, dexoptClassLoaderContext) {
-  EXPECT_CALL(*mock_exec_utils_,
-              DoExecAndReturnCode(WhenSplitBy(
-                  "--",
-                  _,
-                  AllOf(Contains(ListFlag("--class-loader-context-fds=",
-                                          ElementsAre(FdOf(clc_1_), FdOf(clc_2_)))),
-                        Contains(Flag("--class-loader-context=", class_loader_context_)),
-                        Contains(Flag("--classpath-dir=", scratch_path_ + "/a"))))))
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(
+          WhenSplitBy("--",
+                      _,
+                      AllOf(Contains(ListFlag("--class-loader-context-fds=",
+                                              ElementsAre(FdOf(clc_1_), FdOf(clc_2_)))),
+                            Contains(Flag("--class-loader-context=", class_loader_context_)),
+                            Contains(Flag("--classpath-dir=", scratch_path_ + "/a")))),
+          _))
       .WillOnce(Return(0));
   RunDexopt();
 }
@@ -451,20 +470,22 @@
               DoExecAndReturnCode(WhenSplitBy("--",
                                               _,
                                               AllOf(Not(Contains(Flag("--dm-fd=", _))),
-                                                    Not(Contains(Flag("--input-vdex-fd=", _)))))))
+                                                    Not(Contains(Flag("--input-vdex-fd=", _))))),
+                                  _))
       .WillOnce(Return(0));
   RunDexopt();
 }
 
 TEST_F(ArtdTest, dexoptInputVdex) {
   vdex_path_ = artifacts_path_;
-  EXPECT_CALL(
-      *mock_exec_utils_,
-      DoExecAndReturnCode(WhenSplitBy(
-          "--",
-          _,
-          AllOf(Not(Contains(Flag("--dm-fd=", _))),
-                Contains(Flag("--input-vdex-fd=", FdOf(scratch_path_ + "/a/oat/arm64/b.vdex")))))))
+  EXPECT_CALL(*mock_exec_utils_,
+              DoExecAndReturnCode(
+                  WhenSplitBy("--",
+                              _,
+                              AllOf(Not(Contains(Flag("--dm-fd=", _))),
+                                    Contains(Flag("--input-vdex-fd=",
+                                                  FdOf(scratch_path_ + "/a/oat/arm64/b.vdex"))))),
+                  _))
       .WillOnce(Return(0));
   RunDexopt();
 }
@@ -476,7 +497,8 @@
                   WhenSplitBy("--",
                               _,
                               AllOf(Contains(Flag("--dm-fd=", FdOf(scratch_path_ + "/a/b.dm"))),
-                                    Not(Contains(Flag("--input-vdex-fd=", _)))))))
+                                    Not(Contains(Flag("--input-vdex-fd=", _))))),
+                  _))
       .WillOnce(Return(0));
   RunDexopt();
 }
@@ -487,7 +509,8 @@
               DoExecAndReturnCode(WhenSplitBy("--",
                                               AllOf(Not(Contains(Flag("--set-task-profile=", _))),
                                                     Not(Contains(Flag("--set-priority=", _)))),
-                                              Contains(Flag("--compact-dex-level=", "none")))))
+                                              Contains(Flag("--compact-dex-level=", "none"))),
+                                  _))
       .WillOnce(Return(0));
   RunDexopt();
 }
@@ -499,7 +522,8 @@
                   WhenSplitBy("--",
                               AllOf(Contains(Flag("--set-task-profile=", "Dex2OatBootComplete")),
                                     Contains(Flag("--set-priority=", "background"))),
-                              Contains(Flag("--compact-dex-level=", "none")))))
+                              Contains(Flag("--compact-dex-level=", "none"))),
+                  _))
       .WillOnce(Return(0));
   RunDexopt();
 }
@@ -511,7 +535,8 @@
                   WhenSplitBy("--",
                               AllOf(Contains(Flag("--set-task-profile=", "Dex2OatBootComplete")),
                                     Contains(Flag("--set-priority=", "background"))),
-                              Contains(Flag("--compact-dex-level=", "none")))))
+                              Contains(Flag("--compact-dex-level=", "none"))),
+                  _))
       .WillOnce(Return(0));
   RunDexopt();
 }
@@ -523,7 +548,8 @@
                   WhenSplitBy("--",
                               AllOf(Contains(Flag("--set-task-profile=", "Dex2OatBootComplete")),
                                     Contains(Flag("--set-priority=", "background"))),
-                              Not(Contains(Flag("--compact-dex-level=", _))))))
+                              Not(Contains(Flag("--compact-dex-level=", _)))),
+                  _))
       .WillOnce(Return(0));
   RunDexopt();
 }
@@ -545,7 +571,8 @@
                                             Contains(Flag("-Xtarget-sdk-version:", "123")),
                                             Not(Contains("--debuggable")),
                                             Not(Contains(Flag("--app-image-fd=", _))),
-                                            Not(Contains(Flag("-Xhidden-api-policy:", _)))))))
+                                            Not(Contains(Flag("-Xhidden-api-policy:", _))))),
+                          _))
       .WillOnce(Return(0));
   RunDexopt();
 }
@@ -566,7 +593,8 @@
                                       AllOf(Contains(Flag("--compilation-reason=", "bg-dexopt")),
                                             Contains(Flag("-Xtarget-sdk-version:", "456")),
                                             Contains("--debuggable"),
-                                            Contains(Flag("-Xhidden-api-policy:", "enabled"))))))
+                                            Contains(Flag("-Xhidden-api-policy:", "enabled")))),
+                          _))
       .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--app-image-fd=", "art")), Return(0)));
   RunDexopt();
 
@@ -591,7 +619,8 @@
                                     Not(Contains(Flag("-j", _))),
                                     Not(Contains(Flag("-Xms", _))),
                                     Not(Contains(Flag("-Xmx", _))),
-                                    Not(Contains("--compile-individually"))))))
+                                    Not(Contains("--compile-individually")))),
+                  _))
       .WillOnce(Return(0));
   RunDexopt();
 }
@@ -629,7 +658,8 @@
                                     Not(Contains("-Xdeny-art-apex-data-files")),
                                     Contains(Flag("-Xms", "xms")),
                                     Contains(Flag("-Xmx", "xmx")),
-                                    Contains("--compile-individually")))))
+                                    Contains("--compile-individually"))),
+                  _))
       .WillOnce(Return(0));
   RunDexopt();
 }
@@ -645,8 +675,10 @@
   // The default resource control properties don't apply to BOOT.
   EXPECT_CALL(
       *mock_exec_utils_,
-      DoExecAndReturnCode(WhenSplitBy(
-          "--", _, AllOf(Not(Contains(Flag("--cpu-set=", _))), Contains(Not(Flag("-j", _)))))))
+      DoExecAndReturnCode(
+          WhenSplitBy(
+              "--", _, AllOf(Not(Contains(Flag("--cpu-set=", _))), Contains(Not(Flag("-j", _))))),
+          _))
       .WillOnce(Return(0));
   priority_class_ = PriorityClass::BOOT;
   RunDexopt();
@@ -655,9 +687,12 @@
 TEST_F(ArtdTest, dexoptDefaultResourceControlOther) {
   SetDefaultResourceControlProps(mock_props_);
 
-  EXPECT_CALL(*mock_exec_utils_,
-              DoExecAndReturnCode(WhenSplitBy(
-                  "--", _, AllOf(Contains(Flag("--cpu-set=", "0,2")), Contains(Flag("-j", "4"))))))
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(
+          WhenSplitBy(
+              "--", _, AllOf(Contains(Flag("--cpu-set=", "0,2")), Contains(Flag("-j", "4")))),
+          _))
       .Times(3)
       .WillRepeatedly(Return(0));
   priority_class_ = PriorityClass::INTERACTIVE_FAST;
@@ -690,8 +725,10 @@
 
   EXPECT_CALL(
       *mock_exec_utils_,
-      DoExecAndReturnCode(WhenSplitBy(
-          "--", _, AllOf(Contains(Flag("--cpu-set=", "0,1,2,3")), Contains(Flag("-j", "8"))))))
+      DoExecAndReturnCode(
+          WhenSplitBy(
+              "--", _, AllOf(Contains(Flag("--cpu-set=", "0,1,2,3")), Contains(Flag("-j", "8")))),
+          _))
       .WillOnce(Return(0));
   priority_class_ = PriorityClass::BOOT;
   RunDexopt();
@@ -702,8 +739,10 @@
 
   EXPECT_CALL(
       *mock_exec_utils_,
-      DoExecAndReturnCode(WhenSplitBy(
-          "--", _, AllOf(Contains(Flag("--cpu-set=", "0,2,3")), Contains(Flag("-j", "6"))))))
+      DoExecAndReturnCode(
+          WhenSplitBy(
+              "--", _, AllOf(Contains(Flag("--cpu-set=", "0,2,3")), Contains(Flag("-j", "6")))),
+          _))
       .WillOnce(Return(0));
   priority_class_ = PriorityClass::INTERACTIVE_FAST;
   RunDexopt();
@@ -713,9 +752,12 @@
   SetAllResourceControlProps(mock_props_);
 
   // INTERACTIVE always uses the default resource control properties.
-  EXPECT_CALL(*mock_exec_utils_,
-              DoExecAndReturnCode(WhenSplitBy(
-                  "--", _, AllOf(Contains(Flag("--cpu-set=", "0,2")), Contains(Flag("-j", "4"))))))
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(
+          WhenSplitBy(
+              "--", _, AllOf(Contains(Flag("--cpu-set=", "0,2")), Contains(Flag("-j", "4")))),
+          _))
       .WillOnce(Return(0));
   priority_class_ = PriorityClass::INTERACTIVE;
   RunDexopt();
@@ -724,9 +766,11 @@
 TEST_F(ArtdTest, dexoptAllResourceControlBackground) {
   SetAllResourceControlProps(mock_props_);
 
-  EXPECT_CALL(*mock_exec_utils_,
-              DoExecAndReturnCode(WhenSplitBy(
-                  "--", _, AllOf(Contains(Flag("--cpu-set=", "0")), Contains(Flag("-j", "2"))))))
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(
+          WhenSplitBy("--", _, AllOf(Contains(Flag("--cpu-set=", "0")), Contains(Flag("-j", "2")))),
+          _))
       .WillOnce(Return(0));
   priority_class_ = PriorityClass::BACKGROUND;
   RunDexopt();
@@ -734,7 +778,7 @@
 
 TEST_F(ArtdTest, dexoptFailed) {
   dexopt_options_.generateAppImage = true;
-  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_))
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _))
       .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--oat-fd=", "oat")),
                       WithArg<0>(WriteToFdFlag("--output-vdex-fd=", "vdex")),
                       WithArg<0>(WriteToFdFlag("--app-image-fd=", "art")),
@@ -751,13 +795,15 @@
   CreateFile(profile_file);
   CreateFile(dex_file_);
 
-  EXPECT_CALL(*mock_exec_utils_,
-              DoExecAndReturnCode(WhenSplitBy(
-                  "--",
-                  AllOf(Contains(art_root_ + "/bin/art_exec"), Contains("--drop-capabilities")),
-                  AllOf(Contains(art_root_ + "/bin/profman"),
-                        Contains(Flag("--reference-profile-file-fd=", FdOf(profile_file))),
-                        Contains(Flag("--apk-fd=", FdOf(dex_file_)))))))
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(
+          WhenSplitBy("--",
+                      AllOf(Contains(art_root_ + "/bin/art_exec"), Contains("--drop-capabilities")),
+                      AllOf(Contains(art_root_ + "/bin/profman"),
+                            Contains(Flag("--reference-profile-file-fd=", FdOf(profile_file))),
+                            Contains(Flag("--apk-fd=", FdOf(dex_file_))))),
+          _))
       .WillOnce(Return(ProfmanResult::kSkipCompilationSmallDelta));
 
   bool result;
@@ -770,7 +816,7 @@
   CreateFile(profile_file);
   CreateFile(dex_file_);
 
-  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_))
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _))
       .WillOnce(Return(ProfmanResult::kSkipCompilationEmptyProfiles));
 
   bool result;
@@ -791,7 +837,7 @@
   CreateFile(profile_file);
   CreateFile(dex_file_);
 
-  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_)).WillOnce(Return(100));
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _)).WillOnce(Return(100));
 
   bool result;
   ndk::ScopedAStatus status = artd_->isProfileUsable(profile_path_.value(), dex_file_, &result);
@@ -836,14 +882,16 @@
 
   CreateFile(dex_file_);
 
-  EXPECT_CALL(*mock_exec_utils_,
-              DoExecAndReturnCode(WhenSplitBy(
-                  "--",
-                  AllOf(Contains(art_root_ + "/bin/art_exec"), Contains("--drop-capabilities")),
-                  AllOf(Contains(art_root_ + "/bin/profman"),
-                        Contains("--copy-and-update-profile-key"),
-                        Contains(Flag("--profile-file-fd=", FdOf(src_file))),
-                        Contains(Flag("--apk-fd=", FdOf(dex_file_)))))))
+  EXPECT_CALL(
+      *mock_exec_utils_,
+      DoExecAndReturnCode(
+          WhenSplitBy("--",
+                      AllOf(Contains(art_root_ + "/bin/art_exec"), Contains("--drop-capabilities")),
+                      AllOf(Contains(art_root_ + "/bin/profman"),
+                            Contains("--copy-and-update-profile-key"),
+                            Contains(Flag("--profile-file-fd=", FdOf(src_file))),
+                            Contains(Flag("--apk-fd=", FdOf(dex_file_))))),
+          _))
       .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--reference-profile-file-fd=", "def")),
                       Return(ProfmanResult::kCopyAndUpdateSuccess)));
 
@@ -863,7 +911,7 @@
 
   CreateFile(dex_file_);
 
-  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_))
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _))
       .WillOnce(Return(ProfmanResult::kCopyAndUpdateNoUpdate));
 
   bool result;
@@ -892,7 +940,7 @@
 
   CreateFile(dex_file_);
 
-  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_)).WillOnce(Return(100));
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _)).WillOnce(Return(100));
 
   bool result;
   ndk::ScopedAStatus status = artd_->copyAndRewriteProfile(src, &dst, dex_file_, &result);
diff --git a/artd/binder/com/android/server/art/DexoptResult.aidl b/artd/binder/com/android/server/art/DexoptResult.aidl
new file mode 100644
index 0000000..1f1b053
--- /dev/null
+++ b/artd/binder/com/android/server/art/DexoptResult.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+package com.android.server.art;
+
+/**
+ * The result of {@code IArtd.dexopt}.
+ *
+ * @hide
+ */
+parcelable DexoptResult {
+    /** True if the operation is cancelled. */
+    boolean cancelled;
+    /**
+     * The wall time of the dex2oat invocation, in milliseconds, or 0 if dex2oat is not run or if
+     * failed to get the value.
+     */
+    long wallTimeMs;
+    /**
+     * The CPU time of the dex2oat invocation, in milliseconds, or 0 if dex2oat is not run or if
+     * failed to get the value.
+     */
+    long cpuTimeMs;
+}
diff --git a/artd/binder/com/android/server/art/IArtd.aidl b/artd/binder/com/android/server/art/IArtd.aidl
index 9328dab..a3eab2f 100644
--- a/artd/binder/com/android/server/art/IArtd.aidl
+++ b/artd/binder/com/android/server/art/IArtd.aidl
@@ -111,12 +111,12 @@
             int dexoptTrigger);
 
     /**
-     * Dexopts a dex file for the given instruction set. Returns true on success, or false if
-     * cancelled.
+     * Dexopts a dex file for the given instruction set.
      *
      * Throws fatal and non-fatal errors.
      */
-    boolean dexopt(in com.android.server.art.OutputArtifacts outputArtifacts,
+    com.android.server.art.DexoptResult dexopt(
+            in com.android.server.art.OutputArtifacts outputArtifacts,
             @utf8InCpp String dexFile, @utf8InCpp String instructionSet,
             @utf8InCpp String classLoaderContext, @utf8InCpp String compilerFilter,
             in @nullable com.android.server.art.ProfilePath profile,
diff --git a/libartservice/service/api/system-server-current.txt b/libartservice/service/api/system-server-current.txt
index c9ce7cd..6bf9d27 100644
--- a/libartservice/service/api/system-server-current.txt
+++ b/libartservice/service/api/system-server-current.txt
@@ -79,6 +79,8 @@
 
   public static class OptimizeResult.DexFileOptimizeResult {
     method @NonNull public String getActualCompilerFilter();
+    method public long getDex2oatCpuTimeMillis();
+    method public long getDex2oatWallTimeMillis();
     method @NonNull public String getDexFile();
     method @NonNull public String getInstructionSet();
     method public int getStatus();
diff --git a/libartservice/service/java/com/android/server/art/ArtShellCommand.java b/libartservice/service/java/com/android/server/art/ArtShellCommand.java
index b797d8b..5bcae6d 100644
--- a/libartservice/service/java/com/android/server/art/ArtShellCommand.java
+++ b/libartservice/service/java/com/android/server/art/ArtShellCommand.java
@@ -103,10 +103,13 @@
                     for (DexFileOptimizeResult dexFileResult :
                             packageResult.getDexFileOptimizeResults()) {
                         pw.printf("dexFile = %s, instructionSet = %s, compilerFilter = %s, "
-                                        + "status = %s\n",
+                                        + "status = %s, dex2oatWallTimeMillis = %d, "
+                                        + "dex2oatCpuTimeMillis = %d\n",
                                 dexFileResult.getDexFile(), dexFileResult.getInstructionSet(),
                                 dexFileResult.getActualCompilerFilter(),
-                                optimizeStatusToString(dexFileResult.getStatus()));
+                                optimizeStatusToString(dexFileResult.getStatus()),
+                                dexFileResult.getDex2oatWallTimeMillis(),
+                                dexFileResult.getDex2oatCpuTimeMillis());
                     }
                 }
                 return 0;
diff --git a/libartservice/service/java/com/android/server/art/PrimaryDexOptimizer.java b/libartservice/service/java/com/android/server/art/PrimaryDexOptimizer.java
index a63e4e6..b2875e8 100644
--- a/libartservice/service/java/com/android/server/art/PrimaryDexOptimizer.java
+++ b/libartservice/service/java/com/android/server/art/PrimaryDexOptimizer.java
@@ -143,6 +143,8 @@
 
                 for (String isa : Utils.getAllIsas(pkgState)) {
                     @OptimizeResult.OptimizeStatus int status = OptimizeResult.OPTIMIZE_SKIPPED;
+                    long wallTimeMs = 0;
+                    long cpuTimeMs = 0;
                     try {
                         DexoptTarget target = DexoptTarget.builder()
                                                       .setDexInfo(dexInfo)
@@ -168,8 +170,13 @@
                                 ? ProfilePath.tmpRefProfilePath(profile.profilePath)
                                 : null;
 
-                        status = dexoptFile(target, inputProfile, getDexoptNeededResult,
-                                permissionSettings, params.getPriorityClass(), dexoptOptions);
+                        DexoptResult dexoptResult = dexoptFile(target, inputProfile,
+                                getDexoptNeededResult, permissionSettings,
+                                params.getPriorityClass(), dexoptOptions);
+                        status = dexoptResult.cancelled ? OptimizeResult.OPTIMIZE_CANCELLED
+                                                        : OptimizeResult.OPTIMIZE_PERFORMED;
+                        wallTimeMs = dexoptResult.wallTimeMs;
+                        cpuTimeMs = dexoptResult.cpuTimeMs;
                     } catch (ServiceSpecificException e) {
                         // Log the error and continue.
                         Log.e(TAG,
@@ -180,8 +187,8 @@
                                 e);
                         status = OptimizeResult.OPTIMIZE_FAILED;
                     } finally {
-                        results.add(new DexFileOptimizeResult(
-                                dexInfo.dexPath(), isa, compilerFilter, status));
+                        results.add(new DexFileOptimizeResult(dexInfo.dexPath(), isa,
+                                compilerFilter, status, wallTimeMs, cpuTimeMs));
                         if (status != OptimizeResult.OPTIMIZE_SKIPPED
                                 && status != OptimizeResult.OPTIMIZE_PERFORMED) {
                             succeeded = false;
@@ -431,8 +438,8 @@
         return dexoptTrigger;
     }
 
-    private @OptimizeResult.OptimizeStatus int dexoptFile(@NonNull DexoptTarget target,
-            @Nullable ProfilePath profile, @NonNull GetDexoptNeededResult getDexoptNeededResult,
+    private DexoptResult dexoptFile(@NonNull DexoptTarget target, @Nullable ProfilePath profile,
+            @NonNull GetDexoptNeededResult getDexoptNeededResult,
             @NonNull PermissionSettings permissionSettings, @PriorityClass int priorityClass,
             @NonNull DexoptOptions dexoptOptions) throws RemoteException {
         OutputArtifacts outputArtifacts = AidlUtils.buildOutputArtifacts(target.dexInfo().dexPath(),
@@ -441,13 +448,9 @@
         VdexPath inputVdex =
                 getInputVdex(getDexoptNeededResult, target.dexInfo().dexPath(), target.isa());
 
-        if (!mInjector.getArtd().dexopt(outputArtifacts, target.dexInfo().dexPath(), target.isa(),
-                    target.dexInfo().classLoaderContext(), target.compilerFilter(), profile,
-                    inputVdex, priorityClass, dexoptOptions)) {
-            return OptimizeResult.OPTIMIZE_CANCELLED;
-        }
-
-        return OptimizeResult.OPTIMIZE_PERFORMED;
+        return mInjector.getArtd().dexopt(outputArtifacts, target.dexInfo().dexPath(), target.isa(),
+                target.dexInfo().classLoaderContext(), target.compilerFilter(), profile, inputVdex,
+                priorityClass, dexoptOptions);
     }
 
     @Nullable
diff --git a/libartservice/service/java/com/android/server/art/model/OptimizeResult.java b/libartservice/service/java/com/android/server/art/model/OptimizeResult.java
index f1c83d4..e9b1a00 100644
--- a/libartservice/service/java/com/android/server/art/model/OptimizeResult.java
+++ b/libartservice/service/java/com/android/server/art/model/OptimizeResult.java
@@ -144,14 +144,19 @@
         private final @NonNull String mInstructionSet;
         private final @NonNull String mActualCompilerFilter;
         private final @OptimizeStatus int mStatus;
+        private final long mDex2oatWallTimeMillis;
+        private final long mDex2oatCpuTimeMillis;
 
         /** @hide */
         public DexFileOptimizeResult(@NonNull String dexFile, @NonNull String instructionSet,
-                @NonNull String compilerFilter, @OptimizeStatus int status) {
+                @NonNull String compilerFilter, @OptimizeStatus int status,
+                long dex2oatWallTimeMillis, long dex2oatCpuTimeMillis) {
             mDexFile = dexFile;
             mInstructionSet = instructionSet;
             mActualCompilerFilter = compilerFilter;
             mStatus = status;
+            mDex2oatWallTimeMillis = dex2oatWallTimeMillis;
+            mDex2oatCpuTimeMillis = dex2oatCpuTimeMillis;
         }
 
         /** The absolute path to the dex file. */
@@ -173,5 +178,21 @@
         public @OptimizeStatus int getStatus() {
             return mStatus;
         }
+
+        /**
+         * The wall time of the dex2oat invocation, in milliseconds, if dex2oat succeeded or was
+         * cancelled. Returns 0 if dex2oat failed or was not run, or if failed to get the value.
+         */
+        public long getDex2oatWallTimeMillis() {
+            return mDex2oatWallTimeMillis;
+        }
+
+        /**
+         * The CPU time of the dex2oat invocation, in milliseconds, if dex2oat succeeded or was
+         * cancelled. Returns 0 if dex2oat failed or was not run, or if failed to get the value.
+         */
+        public long getDex2oatCpuTimeMillis() {
+            return mDex2oatCpuTimeMillis;
+        }
     }
 }
diff --git a/libartservice/service/javatests/com/android/server/art/DexOptHelperTest.java b/libartservice/service/javatests/com/android/server/art/DexOptHelperTest.java
index 3b4e045..fed5cfa 100644
--- a/libartservice/service/javatests/com/android/server/art/DexOptHelperTest.java
+++ b/libartservice/service/javatests/com/android/server/art/DexOptHelperTest.java
@@ -77,9 +77,11 @@
             new OptimizeParams.Builder("install").setCompilerFilter("speed-profile").build();
     private final List<DexFileOptimizeResult> mPrimaryResults =
             List.of(new DexFileOptimizeResult("/data/app/foo/base.apk", "arm64", "verify",
-                            OptimizeResult.OPTIMIZE_PERFORMED),
+                            OptimizeResult.OPTIMIZE_PERFORMED, 100 /* dex2oatWallTimeMillis */,
+                            400 /* dex2oatCpuTimeMillis */),
                     new DexFileOptimizeResult("/data/app/foo/base.apk", "arm", "verify",
-                            OptimizeResult.OPTIMIZE_FAILED));
+                            OptimizeResult.OPTIMIZE_FAILED, 100 /* dex2oatWallTimeMillis */,
+                            400 /* dex2oatCpuTimeMillis */));
 
     private DexOptHelper mDexOptHelper;
 
diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerParameterizedTest.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerParameterizedTest.java
index 43aac8d..3de422f 100644
--- a/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerParameterizedTest.java
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerParameterizedTest.java
@@ -206,12 +206,15 @@
                 .when(mArtd)
                 .getDexoptNeeded("/data/app/foo/base.apk", "arm64", "PCL[]",
                         mParams.mExpectedCompilerFilter, mParams.mExpectedDexoptTrigger);
-        doReturn(true).when(mArtd).dexopt(
-                deepEq(buildOutputArtifacts("/data/app/foo/base.apk", "arm64",
-                        mParams.mExpectedIsInDalvikCache, permissionSettings)),
-                eq("/data/app/foo/base.apk"), eq("arm64"), eq("PCL[]"),
-                eq(mParams.mExpectedCompilerFilter), isNull() /* profile */,
-                isNull() /* inputVdex */, eq(PriorityClass.INTERACTIVE), deepEq(dexoptOptions));
+        doReturn(createDexoptResult(
+                         false /* cancelled */, 100 /* wallTimeMs */, 400 /* cpuTimeMs */))
+                .when(mArtd)
+                .dexopt(deepEq(buildOutputArtifacts("/data/app/foo/base.apk", "arm64",
+                                mParams.mExpectedIsInDalvikCache, permissionSettings)),
+                        eq("/data/app/foo/base.apk"), eq("arm64"), eq("PCL[]"),
+                        eq(mParams.mExpectedCompilerFilter), isNull() /* profile */,
+                        isNull() /* inputVdex */, eq(PriorityClass.INTERACTIVE),
+                        deepEq(dexoptOptions));
 
         // The second one fails on `dexopt`.
         doReturn(dexoptIsNeeded())
@@ -238,25 +241,31 @@
                 .when(mArtd)
                 .getDexoptNeeded("/data/app/foo/split_0.apk", "arm", "PCL[base.apk]",
                         mParams.mExpectedCompilerFilter, mParams.mExpectedDexoptTrigger);
-        doReturn(true).when(mArtd).dexopt(
-                deepEq(buildOutputArtifacts("/data/app/foo/split_0.apk", "arm",
-                        mParams.mExpectedIsInDalvikCache, permissionSettings)),
-                eq("/data/app/foo/split_0.apk"), eq("arm"), eq("PCL[base.apk]"),
-                eq(mParams.mExpectedCompilerFilter), isNull() /* profile */,
-                isNull() /* inputVdex */, eq(PriorityClass.INTERACTIVE), deepEq(dexoptOptions));
+        doReturn(createDexoptResult(
+                         false /* cancelled */, 200 /* wallTimeMs */, 200 /* cpuTimeMs */))
+                .when(mArtd)
+                .dexopt(deepEq(buildOutputArtifacts("/data/app/foo/split_0.apk", "arm",
+                                mParams.mExpectedIsInDalvikCache, permissionSettings)),
+                        eq("/data/app/foo/split_0.apk"), eq("arm"), eq("PCL[base.apk]"),
+                        eq(mParams.mExpectedCompilerFilter), isNull() /* profile */,
+                        isNull() /* inputVdex */, eq(PriorityClass.INTERACTIVE),
+                        deepEq(dexoptOptions));
 
         assertThat(mPrimaryDexOptimizer.dexopt(mPkgState, mPkg, mOptimizeParams))
                 .comparingElementsUsing(TestingUtils.<DexFileOptimizeResult>deepEquality())
                 .containsExactly(
                         new DexFileOptimizeResult("/data/app/foo/base.apk", "arm64",
-                                mParams.mExpectedCompilerFilter, OptimizeResult.OPTIMIZE_PERFORMED),
+                                mParams.mExpectedCompilerFilter, OptimizeResult.OPTIMIZE_PERFORMED,
+                                100 /* dex2oatWallTimeMillis */, 400 /* dex2oatCpuTimeMillis */),
                         new DexFileOptimizeResult("/data/app/foo/base.apk", "arm",
-                                mParams.mExpectedCompilerFilter, OptimizeResult.OPTIMIZE_FAILED),
+                                mParams.mExpectedCompilerFilter, OptimizeResult.OPTIMIZE_FAILED,
+                                0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */),
                         new DexFileOptimizeResult("/data/app/foo/split_0.apk", "arm64",
-                                mParams.mExpectedCompilerFilter, OptimizeResult.OPTIMIZE_SKIPPED),
+                                mParams.mExpectedCompilerFilter, OptimizeResult.OPTIMIZE_SKIPPED,
+                                0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */),
                         new DexFileOptimizeResult("/data/app/foo/split_0.apk", "arm",
-                                mParams.mExpectedCompilerFilter,
-                                OptimizeResult.OPTIMIZE_PERFORMED));
+                                mParams.mExpectedCompilerFilter, OptimizeResult.OPTIMIZE_PERFORMED,
+                                200 /* dex2oatWallTimeMillis */, 200 /* dex2oatCpuTimeMillis */));
     }
 
     private static class Params {
diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTest.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTest.java
index 03151e7..498657f 100644
--- a/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTest.java
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTest.java
@@ -76,6 +76,9 @@
             | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE
             | DexoptTrigger.COMPILER_FILTER_IS_SAME | DexoptTrigger.COMPILER_FILTER_IS_WORSE;
 
+    private final DexoptResult mDexoptResult =
+            createDexoptResult(false /* cancelled */, 200 /* wallTimeMs */, 200 /* cpuTimeMs */);
+
     private List<ProfilePath> mUsedProfiles;
 
     @Before
@@ -93,7 +96,7 @@
         lenient()
                 .when(mArtd.dexopt(
                         any(), any(), any(), any(), any(), any(), any(), anyInt(), any()))
-                .thenReturn(true);
+                .thenReturn(mDexoptResult);
 
         mUsedProfiles = new ArrayList<>();
     }
@@ -104,35 +107,43 @@
         doReturn(dexoptIsNeeded(ArtifactsLocation.NONE_OR_ERROR))
                 .when(mArtd)
                 .getDexoptNeeded(eq(mDexPath), eq("arm64"), any(), any(), anyInt());
-        doReturn(true).when(mArtd).dexopt(
-                any(), eq(mDexPath), eq("arm64"), any(), any(), any(), isNull(), anyInt(), any());
+        doReturn(mDexoptResult)
+                .when(mArtd)
+                .dexopt(any(), eq(mDexPath), eq("arm64"), any(), any(), any(), isNull(), anyInt(),
+                        any());
 
         // ArtifactsPath, isInDalvikCache=true.
         doReturn(dexoptIsNeeded(ArtifactsLocation.DALVIK_CACHE))
                 .when(mArtd)
                 .getDexoptNeeded(eq(mDexPath), eq("arm"), any(), any(), anyInt());
-        doReturn(true).when(mArtd).dexopt(any(), eq(mDexPath), eq("arm"), any(), any(), any(),
-                deepEq(VdexPath.artifactsPath(
-                        AidlUtils.buildArtifactsPath(mDexPath, "arm", true /* isInDalvikCache */))),
-                anyInt(), any());
+        doReturn(mDexoptResult)
+                .when(mArtd)
+                .dexopt(any(), eq(mDexPath), eq("arm"), any(), any(), any(),
+                        deepEq(VdexPath.artifactsPath(AidlUtils.buildArtifactsPath(
+                                mDexPath, "arm", true /* isInDalvikCache */))),
+                        anyInt(), any());
 
         // ArtifactsPath, isInDalvikCache=false.
         doReturn(dexoptIsNeeded(ArtifactsLocation.NEXT_TO_DEX))
                 .when(mArtd)
                 .getDexoptNeeded(eq(mSplit0DexPath), eq("arm64"), any(), any(), anyInt());
-        doReturn(true).when(mArtd).dexopt(any(), eq(mSplit0DexPath), eq("arm64"), any(), any(),
-                any(),
-                deepEq(VdexPath.artifactsPath(AidlUtils.buildArtifactsPath(
-                        mSplit0DexPath, "arm64", false /* isInDalvikCache */))),
-                anyInt(), any());
+        doReturn(mDexoptResult)
+                .when(mArtd)
+                .dexopt(any(), eq(mSplit0DexPath), eq("arm64"), any(), any(), any(),
+                        deepEq(VdexPath.artifactsPath(AidlUtils.buildArtifactsPath(
+                                mSplit0DexPath, "arm64", false /* isInDalvikCache */))),
+                        anyInt(), any());
 
         // DexMetadataPath.
         doReturn(dexoptIsNeeded(ArtifactsLocation.DM))
                 .when(mArtd)
                 .getDexoptNeeded(eq(mSplit0DexPath), eq("arm"), any(), any(), anyInt());
-        doReturn(true).when(mArtd).dexopt(any(), eq(mSplit0DexPath), eq("arm"), any(), any(), any(),
-                deepEq(VdexPath.dexMetadataPath(AidlUtils.buildDexMetadataPath(mSplit0DexPath))),
-                anyInt(), any());
+        doReturn(mDexoptResult)
+                .when(mArtd)
+                .dexopt(any(), eq(mSplit0DexPath), eq("arm"), any(), any(), any(),
+                        deepEq(VdexPath.dexMetadataPath(
+                                AidlUtils.buildDexMetadataPath(mSplit0DexPath))),
+                        anyInt(), any());
 
         mPrimaryDexOptimizer.dexopt(mPkgState, mPkg, mOptimizeParams);
     }
diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTestBase.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTestBase.java
index 2f2c33f..7308318 100644
--- a/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTestBase.java
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTestBase.java
@@ -133,4 +133,12 @@
         }
         return result;
     }
+
+    protected DexoptResult createDexoptResult(boolean cancelled, long wallTimeMs, long cpuTimeMs) {
+        var result = new DexoptResult();
+        result.cancelled = cancelled;
+        result.wallTimeMs = wallTimeMs;
+        result.cpuTimeMs = cpuTimeMs;
+        return result;
+    }
 }