Add the size of optimized artifacts to OptimizeResult.

Bug: 255566429
Test: m test-art-host-gtest-art_artd_tests
Test: atest ArtServiceTests
Test: adb shell pm art optimize-package -m speed-profile -f com.google.android.gms
Ignore-AOSP-First: ART Services
Change-Id: Icbffe6d56dcecdbac8154284f43d16828373db20
diff --git a/artd/artd.cc b/artd/artd.cc
index 2e86b38..4bdf1bf 100644
--- a/artd/artd.cc
+++ b/artd/artd.cc
@@ -117,9 +117,7 @@
 // would take down the system server.
 constexpr int kLongTimeoutSec = 570;  // 9.5 minutes.
 
-// Deletes a file. Returns the size of the deleted file, or 0 if the deleted file is empty or an
-// error occurs.
-int64_t GetSizeAndDeleteFile(const std::string& path) {
+std::optional<int64_t> GetSize(std::string_view path) {
   std::error_code ec;
   int64_t size = std::filesystem::file_size(path, ec);
   if (ec) {
@@ -127,15 +125,26 @@
     if (ec.value() != ENOENT) {
       LOG(ERROR) << "Failed to get the file size of '{}': {}"_format(path, ec.message());
     }
+    return std::nullopt;
+  }
+  return size;
+}
+
+// Deletes a file. Returns the size of the deleted file, or 0 if the deleted file is empty or an
+// error occurs.
+int64_t GetSizeAndDeleteFile(const std::string& path) {
+  std::optional<int64_t> size = GetSize(path);
+  if (!size.has_value()) {
     return 0;
   }
 
+  std::error_code ec;
   if (!std::filesystem::remove(path, ec)) {
     LOG(ERROR) << "Failed to remove '{}': {}"_format(path, ec.message());
     return 0;
   }
 
-  return size;
+  return size.value();
 }
 
 std::string EscapeErrorMessage(const std::string& message) {
@@ -860,8 +869,19 @@
     return NonFatal("dex2oat returned an unexpected code: %d"_format(result.value()));
   }
 
-  NewFile::CommitAllOrAbandon(files_to_commit, files_to_delete);
+  int64_t size_bytes = 0;
+  int64_t size_before_bytes = 0;
+  for (const NewFile* file : files_to_commit) {
+    size_bytes += GetSize(file->TempPath()).value_or(0);
+    size_before_bytes += GetSize(file->FinalPath()).value_or(0);
+  }
+  for (std::string_view path : files_to_delete) {
+    size_before_bytes += GetSize(path).value_or(0);
+  }
+  OR_RETURN_NON_FATAL(NewFile::CommitAllOrAbandon(files_to_commit, files_to_delete));
 
+  _aidl_return->sizeBytes = size_bytes;
+  _aidl_return->sizeBeforeBytes = size_before_bytes;
   return ScopedAStatus::ok();
 }
 
diff --git a/artd/artd_test.cc b/artd/artd_test.cc
index a2ba7f5..c2db04b 100644
--- a/artd/artd_test.cc
+++ b/artd/artd_test.cc
@@ -518,7 +518,10 @@
   RunDexopt(EX_NONE,
             AllOf(Field(&DexoptResult::cancelled, false),
                   Field(&DexoptResult::wallTimeMs, 100),
-                  Field(&DexoptResult::cpuTimeMs, 400)));
+                  Field(&DexoptResult::cpuTimeMs, 400),
+                  Field(&DexoptResult::sizeBytes, strlen("oat") + strlen("vdex")),
+                  Field(&DexoptResult::sizeBeforeBytes,
+                        strlen("old_art") + strlen("old_oat") + strlen("old_vdex"))));
 
   CheckContent(scratch_path_ + "/a/oat/arm64/b.odex", "oat");
   CheckContent(scratch_path_ + "/a/oat/arm64/b.vdex", "vdex");
@@ -874,6 +877,25 @@
   CheckContent(scratch_path_ + "/a/oat/arm64/b.art", "old_art");
 }
 
+TEST_F(ArtdTest, dexoptFailedToCommit) {
+  std::unique_ptr<ScopeGuard<std::function<void()>>> scoped_inaccessible;
+  std::unique_ptr<ScopeGuard<std::function<void()>>> scoped_unroot;
+
+  EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _))
+      .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--oat-fd=", "new_oat")),
+                      WithArg<0>(WriteToFdFlag("--output-vdex-fd=", "new_vdex")),
+                      [&](auto, auto, auto) {
+                        scoped_inaccessible = std::make_unique<ScopeGuard<std::function<void()>>>(
+                            ScopedInaccessible(scratch_path_ + "/a/oat/arm64"));
+                        scoped_unroot =
+                            std::make_unique<ScopeGuard<std::function<void()>>>(ScopedUnroot());
+                        return 0;
+                      }));
+
+  RunDexopt(EX_SERVICE_SPECIFIC,
+            AllOf(Field(&DexoptResult::sizeBytes, 0), Field(&DexoptResult::sizeBeforeBytes, 0)));
+}
+
 TEST_F(ArtdTest, dexoptCancelledBeforeDex2oat) {
   std::shared_ptr<IArtdCancellationSignal> cancellation_signal;
   ASSERT_TRUE(artd_->createCancellationSignal(&cancellation_signal).isOk());
diff --git a/artd/binder/com/android/server/art/DexoptResult.aidl b/artd/binder/com/android/server/art/DexoptResult.aidl
index 1f1b053..52df54d 100644
--- a/artd/binder/com/android/server/art/DexoptResult.aidl
+++ b/artd/binder/com/android/server/art/DexoptResult.aidl
@@ -34,4 +34,15 @@
      * failed to get the value.
      */
     long cpuTimeMs;
+    /**
+     * The total size, in bytes, of the optimized artifacts, or 0 if dex2oat fails, is cancelled, or
+     * is not run.
+     */
+    long sizeBytes;
+    /**
+     * The total size, in bytes, of the previous optimized artifacts that have been replaced, or
+     * 0 if there were no previous optimized artifacts or dex2oat fails, is cancelled, or is not
+     * run.
+     */
+    long sizeBeforeBytes;
 }
diff --git a/libartservice/service/api/system-server-current.txt b/libartservice/service/api/system-server-current.txt
index 0fdabff..fc44bcd 100644
--- a/libartservice/service/api/system-server-current.txt
+++ b/libartservice/service/api/system-server-current.txt
@@ -135,6 +135,8 @@
     method public long getDex2oatCpuTimeMillis();
     method public long getDex2oatWallTimeMillis();
     method @NonNull public String getDexContainerFile();
+    method public long getSizeBeforeBytes();
+    method public long getSizeBytes();
     method public int getStatus();
     method public boolean isPrimaryAbi();
   }
diff --git a/libartservice/service/java/com/android/server/art/ArtShellCommand.java b/libartservice/service/java/com/android/server/art/ArtShellCommand.java
index d0986c1..5540ac3 100644
--- a/libartservice/service/java/com/android/server/art/ArtShellCommand.java
+++ b/libartservice/service/java/com/android/server/art/ArtShellCommand.java
@@ -326,12 +326,13 @@
                     packageResult.getDexContainerFileOptimizeResults()) {
                 pw.printf("dexContainerFile = %s, isPrimaryAbi = %b, abi = %s, "
                                 + "compilerFilter = %s, status = %s, "
-                                + "dex2oatWallTimeMillis = %d, dex2oatCpuTimeMillis = %d\n",
+                                + "dex2oatWallTimeMillis = %d, dex2oatCpuTimeMillis = %d, "
+                                + "sizeBytes = %d, sizeBeforeBytes = %d\n",
                         fileResult.getDexContainerFile(), fileResult.isPrimaryAbi(),
                         fileResult.getAbi(), fileResult.getActualCompilerFilter(),
                         optimizeStatusToString(fileResult.getStatus()),
-                        fileResult.getDex2oatWallTimeMillis(),
-                        fileResult.getDex2oatCpuTimeMillis());
+                        fileResult.getDex2oatWallTimeMillis(), fileResult.getDex2oatCpuTimeMillis(),
+                        fileResult.getSizeBytes(), fileResult.getSizeBeforeBytes());
             }
         }
     }
diff --git a/libartservice/service/java/com/android/server/art/DexOptimizer.java b/libartservice/service/java/com/android/server/art/DexOptimizer.java
index 356d74b..efc25f1 100644
--- a/libartservice/service/java/com/android/server/art/DexOptimizer.java
+++ b/libartservice/service/java/com/android/server/art/DexOptimizer.java
@@ -147,6 +147,8 @@
                     @OptimizeResult.OptimizeStatus int status = OptimizeResult.OPTIMIZE_SKIPPED;
                     long wallTimeMs = 0;
                     long cpuTimeMs = 0;
+                    long sizeBytes = 0;
+                    long sizeBeforeBytes = 0;
                     try {
                         var target = DexoptTarget.<DexInfoType>builder()
                                                       .setDexInfo(dexInfo)
@@ -186,6 +188,8 @@
                                                         : OptimizeResult.OPTIMIZE_PERFORMED;
                         wallTimeMs = dexoptResult.wallTimeMs;
                         cpuTimeMs = dexoptResult.cpuTimeMs;
+                        sizeBytes = dexoptResult.sizeBytes;
+                        sizeBeforeBytes = dexoptResult.sizeBeforeBytes;
 
                         if (status == OptimizeResult.OPTIMIZE_CANCELLED) {
                             return results;
@@ -202,7 +206,7 @@
                     } finally {
                         results.add(new DexContainerFileOptimizeResult(dexInfo.dexPath(),
                                 abi.isPrimaryAbi(), abi.name(), compilerFilter, status, wallTimeMs,
-                                cpuTimeMs));
+                                cpuTimeMs, sizeBytes, sizeBeforeBytes));
                         if (status != OptimizeResult.OPTIMIZE_SKIPPED
                                 && status != OptimizeResult.OPTIMIZE_PERFORMED) {
                             succeeded = false;
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 6714f9a..b777e47 100644
--- a/libartservice/service/java/com/android/server/art/model/OptimizeResult.java
+++ b/libartservice/service/java/com/android/server/art/model/OptimizeResult.java
@@ -157,11 +157,14 @@
         private final @OptimizeStatus int mStatus;
         private final long mDex2oatWallTimeMillis;
         private final long mDex2oatCpuTimeMillis;
+        private final long mSizeBytes;
+        private final long mSizeBeforeBytes;
 
         /** @hide */
         public DexContainerFileOptimizeResult(@NonNull String dexContainerFile,
                 boolean isPrimaryAbi, @NonNull String abi, @NonNull String compilerFilter,
-                @OptimizeStatus int status, long dex2oatWallTimeMillis, long dex2oatCpuTimeMillis) {
+                @OptimizeStatus int status, long dex2oatWallTimeMillis, long dex2oatCpuTimeMillis,
+                long sizeBytes, long sizeBeforeBytes) {
             mDexContainerFile = dexContainerFile;
             mIsPrimaryAbi = isPrimaryAbi;
             mAbi = abi;
@@ -169,6 +172,8 @@
             mStatus = status;
             mDex2oatWallTimeMillis = dex2oatWallTimeMillis;
             mDex2oatCpuTimeMillis = dex2oatCpuTimeMillis;
+            mSizeBytes = sizeBytes;
+            mSizeBeforeBytes = sizeBeforeBytes;
         }
 
         /** The absolute path to the dex container file. */
@@ -222,5 +227,22 @@
         public @DurationMillisLong long getDex2oatCpuTimeMillis() {
             return mDex2oatCpuTimeMillis;
         }
+
+        /**
+         * The total size, in bytes, of the optimized artifacts. Returns 0 if {@link #getStatus()}
+         * is not {@link #OPTIMIZE_PERFORMED}.
+         */
+        public long getSizeBytes() {
+            return mSizeBytes;
+        }
+
+        /**
+         * The total size, in bytes, of the previous optimized artifacts that has been replaced.
+         * Returns 0 if there were no previous optimized artifacts or {@link #getStatus()} is not
+         * {@link #OPTIMIZE_PERFORMED}.
+         */
+        public long getSizeBeforeBytes() {
+            return mSizeBeforeBytes;
+        }
     }
 }
diff --git a/libartservice/service/javatests/com/android/server/art/DexOptHelperTest.java b/libartservice/service/javatests/com/android/server/art/DexOptHelperTest.java
index e12cd5d..0653efb 100644
--- a/libartservice/service/javatests/com/android/server/art/DexOptHelperTest.java
+++ b/libartservice/service/javatests/com/android/server/art/DexOptHelperTest.java
@@ -561,12 +561,14 @@
             String dexPath, boolean partialFailure) {
         return List.of(new DexContainerFileOptimizeResult(dexPath, true /* isPrimaryAbi */,
                                "arm64-v8a", "verify", OptimizeResult.OPTIMIZE_PERFORMED,
-                               100 /* dex2oatWallTimeMillis */, 400 /* dex2oatCpuTimeMillis */),
+                               100 /* dex2oatWallTimeMillis */, 400 /* dex2oatCpuTimeMillis */,
+                               0 /* sizeBytes */, 0 /* sizeBeforeBytes */),
                 new DexContainerFileOptimizeResult(dexPath, false /* isPrimaryAbi */, "armeabi-v7a",
                         "verify",
                         partialFailure ? OptimizeResult.OPTIMIZE_FAILED
                                        : OptimizeResult.OPTIMIZE_PERFORMED,
-                        100 /* dex2oatWallTimeMillis */, 400 /* dex2oatCpuTimeMillis */));
+                        100 /* dex2oatWallTimeMillis */, 400 /* dex2oatCpuTimeMillis */,
+                        0 /* sizeBytes */, 0 /* sizeBeforeBytes */));
     }
 
     private void checkPackageResult(OptimizeResult result, int index, String packageName,
diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerParameterizedTest.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerParameterizedTest.java
index bff34ee..bc5e3b6 100644
--- a/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerParameterizedTest.java
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerParameterizedTest.java
@@ -216,8 +216,8 @@
                 .when(mArtd)
                 .getDexoptNeeded("/data/app/foo/base.apk", "arm64", "PCL[]",
                         mParams.mExpectedCompilerFilter, mParams.mExpectedDexoptTrigger);
-        doReturn(createDexoptResult(
-                         false /* cancelled */, 100 /* wallTimeMs */, 400 /* cpuTimeMs */))
+        doReturn(createDexoptResult(false /* cancelled */, 100 /* wallTimeMs */,
+                         400 /* cpuTimeMs */, 30000 /* sizeBytes */, 32000 /* sizeBeforeBytes */))
                 .when(mArtd)
                 .dexopt(deepEq(buildOutputArtifacts("/data/app/foo/base.apk", "arm64",
                                 mParams.mExpectedIsInDalvikCache, permissionSettings)),
@@ -251,8 +251,8 @@
                 .when(mArtd)
                 .getDexoptNeeded("/data/app/foo/split_0.apk", "arm", "PCL[base.apk]",
                         mParams.mExpectedCompilerFilter, mParams.mExpectedDexoptTrigger);
-        doReturn(createDexoptResult(
-                         false /* cancelled */, 200 /* wallTimeMs */, 200 /* cpuTimeMs */))
+        doReturn(createDexoptResult(false /* cancelled */, 200 /* wallTimeMs */,
+                         200 /* cpuTimeMs */, 10000 /* sizeBytes */, 0 /* sizeBeforeBytes */))
                 .when(mArtd)
                 .dexopt(deepEq(buildOutputArtifacts("/data/app/foo/split_0.apk", "arm",
                                 mParams.mExpectedIsInDalvikCache, permissionSettings)),
@@ -267,19 +267,23 @@
                         new DexContainerFileOptimizeResult("/data/app/foo/base.apk",
                                 true /* isPrimaryAbi */, "arm64-v8a",
                                 mParams.mExpectedCompilerFilter, OptimizeResult.OPTIMIZE_PERFORMED,
-                                100 /* dex2oatWallTimeMillis */, 400 /* dex2oatCpuTimeMillis */),
+                                100 /* dex2oatWallTimeMillis */, 400 /* dex2oatCpuTimeMillis */,
+                                30000 /* sizeBytes */, 32000 /* sizeBeforeBytes */),
                         new DexContainerFileOptimizeResult("/data/app/foo/base.apk",
                                 false /* isPrimaryAbi */, "armeabi-v7a",
                                 mParams.mExpectedCompilerFilter, OptimizeResult.OPTIMIZE_FAILED,
-                                0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */),
+                                0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */,
+                                0 /* sizeBytes */, 0 /* sizeBeforeBytes */),
                         new DexContainerFileOptimizeResult("/data/app/foo/split_0.apk",
                                 true /* isPrimaryAbi */, "arm64-v8a",
                                 mParams.mExpectedCompilerFilter, OptimizeResult.OPTIMIZE_SKIPPED,
-                                0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */),
+                                0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */,
+                                0 /* sizeBytes */, 0 /* sizeBeforeBytes */),
                         new DexContainerFileOptimizeResult("/data/app/foo/split_0.apk",
                                 false /* isPrimaryAbi */, "armeabi-v7a",
                                 mParams.mExpectedCompilerFilter, OptimizeResult.OPTIMIZE_PERFORMED,
-                                200 /* dex2oatWallTimeMillis */, 200 /* dex2oatCpuTimeMillis */));
+                                200 /* dex2oatWallTimeMillis */, 200 /* dex2oatCpuTimeMillis */,
+                                10000 /* sizeBytes */, 0 /* sizeBeforeBytes */));
     }
 
     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 ff74e15..3aee484 100644
--- a/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTest.java
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTest.java
@@ -92,8 +92,7 @@
             | 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 final DexoptResult mDexoptResult = createDexoptResult(false /* cancelled */);
 
     private PrimaryDexOptimizer mPrimaryDexOptimizer;
 
@@ -467,8 +466,7 @@
 
         doAnswer(invocation -> {
             verify(artdCancellationSignal).cancel();
-            return createDexoptResult(
-                    true /* cancelled */, 200 /* wallTimeMs */, 200 /* cpuTimeMs */);
+            return createDexoptResult(true /* cancelled */);
         })
                 .when(mArtd)
                 .dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(), any(),
@@ -501,8 +499,7 @@
         doAnswer(invocation -> {
             dexoptStarted.release();
             assertThat(dexoptCancelled.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
-            return createDexoptResult(
-                    true /* cancelled */, 200 /* wallTimeMs */, 200 /* cpuTimeMs */);
+            return createDexoptResult(true /* cancelled */);
         })
                 .when(mArtd)
                 .dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(), any(),
diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTestBase.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTestBase.java
index 9b272d9..15b3f4b 100644
--- a/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTestBase.java
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexOptimizerTestBase.java
@@ -178,11 +178,19 @@
         return result;
     }
 
-    protected DexoptResult createDexoptResult(boolean cancelled, long wallTimeMs, long cpuTimeMs) {
+    protected DexoptResult createDexoptResult(boolean cancelled, long wallTimeMs, long cpuTimeMs,
+            long sizeBytes, long sizeBeforeBytes) {
         var result = new DexoptResult();
         result.cancelled = cancelled;
         result.wallTimeMs = wallTimeMs;
         result.cpuTimeMs = cpuTimeMs;
+        result.sizeBytes = sizeBytes;
+        result.sizeBeforeBytes = sizeBeforeBytes;
         return result;
     }
+
+    protected DexoptResult createDexoptResult(boolean cancelled) {
+        return createDexoptResult(cancelled, 0 /* wallTimeMs */, 0 /* cpuTimeMs */,
+                0 /* sizeBytes */, 0 /* sizeBeforeBytes */);
+    }
 }
diff --git a/libartservice/service/javatests/com/android/server/art/SecondaryDexOptimizerTest.java b/libartservice/service/javatests/com/android/server/art/SecondaryDexOptimizerTest.java
index 4e9a7f1..a9518d3 100644
--- a/libartservice/service/javatests/com/android/server/art/SecondaryDexOptimizerTest.java
+++ b/libartservice/service/javatests/com/android/server/art/SecondaryDexOptimizerTest.java
@@ -164,16 +164,20 @@
                 .containsExactly(
                         new DexContainerFileOptimizeResult(DEX_1, true /* isPrimaryAbi */,
                                 "arm64-v8a", "speed-profile", OptimizeResult.OPTIMIZE_PERFORMED,
-                                0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */),
+                                0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */,
+                                0 /* sizeBytes */, 0 /* sizeBeforeBytes */),
                         new DexContainerFileOptimizeResult(DEX_2, true /* isPrimaryAbi */,
                                 "arm64-v8a", "speed", OptimizeResult.OPTIMIZE_PERFORMED,
-                                0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */),
+                                0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */,
+                                0 /* sizeBytes */, 0 /* sizeBeforeBytes */),
                         new DexContainerFileOptimizeResult(DEX_2, false /* isPrimaryAbi */,
                                 "armeabi-v7a", "speed", OptimizeResult.OPTIMIZE_PERFORMED,
-                                0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */),
+                                0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */,
+                                0 /* sizeBytes */, 0 /* sizeBeforeBytes */),
                         new DexContainerFileOptimizeResult(DEX_3, true /* isPrimaryAbi */,
                                 "arm64-v8a", "verify", OptimizeResult.OPTIMIZE_PERFORMED,
-                                0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */));
+                                0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */,
+                                0 /* sizeBytes */, 0 /* sizeBeforeBytes */));
 
         // It should use profile for dex 1.
 
@@ -307,6 +311,8 @@
         result.cancelled = false;
         result.wallTimeMs = 0;
         result.cpuTimeMs = 0;
+        result.sizeBytes = 0;
+        result.sizeBeforeBytes = 0;
         return result;
     }