Calculate ART managed file stats.

Bug: 317081212
Test: atest ArtServiceTests
Change-Id: Iede2d323995255b2414150b025ef125b9ebbc661
diff --git a/artd/artd.cc b/artd/artd.cc
index 8269afb..42eb450 100644
--- a/artd/artd.cc
+++ b/artd/artd.cc
@@ -1290,6 +1290,44 @@
   return ScopedAStatus::ok();
 }
 
+ScopedAStatus Artd::getArtifactsSize(const ArtifactsPath& in_artifactsPath, int64_t* _aidl_return) {
+  std::string oat_path = OR_RETURN_FATAL(BuildOatPath(in_artifactsPath));
+  *_aidl_return = 0;
+  *_aidl_return += GetSize(oat_path).value_or(0);
+  *_aidl_return += GetSize(OatPathToVdexPath(oat_path)).value_or(0);
+  *_aidl_return += GetSize(OatPathToArtPath(oat_path)).value_or(0);
+  return ScopedAStatus::ok();
+}
+
+ScopedAStatus Artd::getVdexFileSize(const VdexPath& in_vdexPath, int64_t* _aidl_return) {
+  std::string vdex_path = OR_RETURN_FATAL(BuildVdexPath(in_vdexPath));
+  *_aidl_return = GetSize(vdex_path).value_or(0);
+  return ScopedAStatus::ok();
+}
+
+ScopedAStatus Artd::getRuntimeArtifactsSize(const RuntimeArtifactsPath& in_runtimeArtifactsPath,
+                                            int64_t* _aidl_return) {
+  OR_RETURN_FATAL(ValidateRuntimeArtifactsPath(in_runtimeArtifactsPath));
+  *_aidl_return = 0;
+  Result<std::string> android_data = GetAndroidDataOrError();
+  Result<std::string> android_expand = GetAndroidExpandOrError();
+  if (!android_data.ok() || !android_expand.ok()) {
+    LOG(ERROR) << "Failed to get the path to ANDROID_DATA or ANDROID_EXPAND";
+    return ScopedAStatus::ok();
+  }
+  for (const std::string& file : ListRuntimeArtifactsFiles(
+           android_data.value(), android_expand.value(), in_runtimeArtifactsPath)) {
+    *_aidl_return += GetSize(file).value_or(0);
+  }
+  return ScopedAStatus::ok();
+}
+
+ScopedAStatus Artd::getProfileSize(const ProfilePath& in_profile, int64_t* _aidl_return) {
+  std::string profile_path = OR_RETURN_FATAL(BuildProfileOrDmPath(in_profile));
+  *_aidl_return = GetSize(profile_path).value_or(0);
+  return ScopedAStatus::ok();
+}
+
 Result<void> Artd::Start() {
   OR_RETURN(SetLogVerbosity());
   MemMap::Init();
diff --git a/artd/artd.h b/artd/artd.h
index c670847..d3a47f9 100644
--- a/artd/artd.h
+++ b/artd/artd.h
@@ -177,6 +177,20 @@
       const aidl::com::android::server::art::RuntimeArtifactsPath& in_runtimeArtifactsPath,
       int64_t* _aidl_return) override;
 
+  ndk::ScopedAStatus getArtifactsSize(
+      const aidl::com::android::server::art::ArtifactsPath& in_artifactsPath,
+      int64_t* _aidl_return) override;
+
+  ndk::ScopedAStatus getVdexFileSize(const aidl::com::android::server::art::VdexPath& in_vdexPath,
+                                     int64_t* _aidl_return) override;
+
+  ndk::ScopedAStatus getRuntimeArtifactsSize(
+      const aidl::com::android::server::art::RuntimeArtifactsPath& in_runtimeArtifactsPath,
+      int64_t* _aidl_return) override;
+
+  ndk::ScopedAStatus getProfileSize(const aidl::com::android::server::art::ProfilePath& in_profile,
+                                    int64_t* _aidl_return) override;
+
   android::base::Result<void> Start();
 
  private:
diff --git a/artd/artd_test.cc b/artd/artd_test.cc
index 6660bcc..79e6858 100644
--- a/artd/artd_test.cc
+++ b/artd/artd_test.cc
@@ -2331,6 +2331,92 @@
   }
 }
 
+TEST_F(ArtdTest, getArtifactsSize) {
+  std::string oat_dir = scratch_path_ + "/a/oat/arm64";
+  CreateFile(oat_dir + "/b.odex", std::string(1, '*'));
+  CreateFile(oat_dir + "/b.vdex", std::string(2, '*'));
+  CreateFile(oat_dir + "/b.art", std::string(4, '*'));
+
+  // Irrelevant.
+  CreateFile(oat_dir + "/c.vdex", std::string(8, '*'));
+
+  int64_t aidl_return = -1;
+  ASSERT_TRUE(
+      artd_
+          ->getArtifactsSize(
+              {.dexPath = scratch_path_ + "/a/b.apk", .isa = "arm64", .isInDalvikCache = false},
+              &aidl_return)
+          .isOk());
+  EXPECT_EQ(aidl_return, 1 + 2 + 4);
+}
+
+TEST_F(ArtdTest, getVdexFileSize) {
+  std::string oat_dir = scratch_path_ + "/a/oat/arm64";
+  CreateFile(oat_dir + "/b.vdex", std::string(1, '*'));
+
+  // Irrelevant.
+  CreateFile(oat_dir + "/b.odex", std::string(2, '*'));
+  CreateFile(oat_dir + "/b.art", std::string(4, '*'));
+  CreateFile(oat_dir + "/c.vdex", std::string(8, '*'));
+
+  int64_t aidl_return = -1;
+  ASSERT_TRUE(artd_
+                  ->getVdexFileSize(ArtifactsPath{.dexPath = scratch_path_ + "/a/b.apk",
+                                                  .isa = "arm64",
+                                                  .isInDalvikCache = false},
+                                    &aidl_return)
+                  .isOk());
+  EXPECT_EQ(aidl_return, 1);
+}
+
+TEST_F(ArtdTest, getRuntimeArtifactsSize) {
+  CreateFile(android_data_ + "/user_de/0/com.android.foo/cache/oat_primary/arm64/base.art",
+             std::string(1, '*'));
+  CreateFile(android_data_ + "/user/0/com.android.foo/cache/oat_primary/arm64/base.art",
+             std::string(2, '*'));
+  CreateFile(android_data_ + "/user/1/com.android.foo/cache/oat_primary/arm64/base.art",
+             std::string(4, '*'));
+  CreateFile(
+      android_expand_ + "/123456-7890/user/1/com.android.foo/cache/oat_primary/arm64/base.art",
+      std::string(8, '*'));
+
+  // Irrelevant.
+  CreateFile(android_expand_ + "/user/0/com.android.foo/cache/oat_primary/arm64/different_dex.art",
+             std::string(16, '*'));
+
+  int64_t aidl_return = -1;
+  ASSERT_TRUE(
+      artd_
+          ->getRuntimeArtifactsSize(
+              {.packageName = "com.android.foo", .dexPath = "/a/b/base.apk", .isa = "arm64"},
+              &aidl_return)
+          .isOk());
+
+  EXPECT_EQ(aidl_return, 1 + 2 + 4 + 8);
+}
+
+TEST_F(ArtdTest, getProfileSize) {
+  CreateFile(android_data_ + "/misc/profiles/cur/0/com.android.foo/primary.prof",
+             std::string(1, '*'));
+
+  // Irrelevant.
+  CreateFile(android_data_ + "/misc/profiles/cur/0/com.android.foo/split_0.split.prof",
+             std::string(2, '*'));
+  CreateFile(android_data_ + "/misc/profiles/cur/0/com.android.bar/primary.prof",
+             std::string(4, '*'));
+  CreateFile(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof",
+             std::string(8, '*'));
+
+  int64_t aidl_return = -1;
+  ASSERT_TRUE(artd_
+                  ->getProfileSize(
+                      PrimaryCurProfilePath{
+                          .userId = 0, .packageName = "com.android.foo", .profileName = "primary"},
+                      &aidl_return)
+                  .isOk());
+  EXPECT_EQ(aidl_return, 1);
+}
+
 }  // namespace
 }  // namespace artd
 }  // namespace art
diff --git a/artd/binder/com/android/server/art/IArtd.aidl b/artd/binder/com/android/server/art/IArtd.aidl
index bc543ef..f8c4e5f 100644
--- a/artd/binder/com/android/server/art/IArtd.aidl
+++ b/artd/binder/com/android/server/art/IArtd.aidl
@@ -199,4 +199,39 @@
      */
     long deleteRuntimeArtifacts(
             in com.android.server.art.RuntimeArtifactsPath runtimeArtifactsPath);
+
+    /**
+     * Returns the size of the dexopt artifacts, in bytes, or 0 if they don't exist or a non-fatal
+     * error occurred.
+     *
+     * Throws fatal errors. Logs and ignores non-fatal errors.
+     */
+    long getArtifactsSize(in com.android.server.art.ArtifactsPath artifactsPath);
+
+    /**
+     * Returns the size of the vdex file, in bytes, or 0 if it doesn't exist or a non-fatal error
+     * occurred.
+     *
+     * Throws fatal errors. Logs and ignores non-fatal errors.
+     */
+    long getVdexFileSize(in com.android.server.art.VdexPath vdexPath);
+
+    /**
+     * Returns the size of the runtime artifacts, in bytes, or 0 if they don't exist or a non-fatal
+     * error occurred.
+     *
+     * Throws fatal errors. Logs and ignores non-fatal errors.
+     */
+    long getRuntimeArtifactsSize(
+            in com.android.server.art.RuntimeArtifactsPath runtimeArtifactsPath);
+
+    /**
+     * Returns the size of the profile, in bytes, or 0 if it doesn't exist or a non-fatal error
+     * occurred.
+     *
+     * Operates on the whole DM file if given one.
+     *
+     * Throws fatal errors. Logs and ignores non-fatal errors.
+     */
+    long getProfileSize(in com.android.server.art.ProfilePath profile);
 }
diff --git a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
index 6c69225..c1969f4 100644
--- a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
+++ b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
@@ -58,6 +58,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.LocalManagerRegistry;
 import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.ArtManagedFileStats;
 import com.android.server.art.model.BatchDexoptParams;
 import com.android.server.art.model.Config;
 import com.android.server.art.model.DeleteResult;
@@ -874,6 +875,57 @@
     }
 
     /**
+     * Returns the statistics of the files managed by ART of a package.
+     *
+     * @throws IllegalArgumentException if the package is not found
+     * @throws IllegalStateException if the operation encounters an error that should never happen
+     *         (e.g., an internal logic error).
+     *
+     * @hide
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @NonNull
+    public ArtManagedFileStats getArtManagedFileStats(
+            @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName) {
+        PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
+        AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
+
+        try {
+            long artifactsSize = 0;
+            long refProfilesSize = 0;
+            long curProfilesSize = 0;
+            IArtd artd = mInjector.getArtd();
+
+            UsableArtifactLists artifactLists =
+                    mInjector.getArtFileManager().getUsableArtifacts(pkgState, pkg);
+            for (ArtifactsPath artifacts : artifactLists.artifacts()) {
+                artifactsSize += artd.getArtifactsSize(artifacts);
+            }
+            for (VdexPath vdexFile : artifactLists.vdexFiles()) {
+                artifactsSize += artd.getVdexFileSize(vdexFile);
+            }
+            for (RuntimeArtifactsPath runtimeArtifacts : artifactLists.runtimeArtifacts()) {
+                artifactsSize += artd.getRuntimeArtifactsSize(runtimeArtifacts);
+            }
+
+            ProfileLists profileLists = mInjector.getArtFileManager().getProfiles(
+                    pkgState, pkg, true /* alsoForSecondaryDex */, true /* excludeDexNotFound */);
+            for (ProfilePath profile : profileLists.refProfiles()) {
+                refProfilesSize += artd.getProfileSize(profile);
+            }
+            for (ProfilePath profile : profileLists.curProfiles()) {
+                curProfilesSize += artd.getProfileSize(profile);
+            }
+
+            return ArtManagedFileStats.create(artifactsSize, refProfilesSize, curProfilesSize);
+        } catch (RemoteException e) {
+            Utils.logArtdException(e);
+            return ArtManagedFileStats.create(
+                    0 /* artifactsSize */, 0 /* refProfilesSize */, 0 /* curProfilesSize */);
+        }
+    }
+
+    /**
      * Cleans up obsolete profiles and artifacts.
      *
      * This is done in a mark-and-sweep approach.
diff --git a/libartservice/service/java/com/android/server/art/model/ArtManagedFileStats.java b/libartservice/service/java/com/android/server/art/model/ArtManagedFileStats.java
new file mode 100644
index 0000000..7a60163
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/model/ArtManagedFileStats.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2023 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.model;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import com.android.internal.annotations.Immutable;
+
+import com.google.auto.value.AutoValue;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * Statistics of the files managed by ART of a package.
+ *
+ * @hide
+ */
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+@Immutable
+@AutoValue
+@SuppressWarnings("AutoValueImmutableFields") // Can't use ImmutableMap because it's in Guava.
+public abstract class ArtManagedFileStats {
+    /**
+     * Dexopt artifacts and runtime artifacts, excluding stale ones.
+     *
+     * Dexopt artifacts become stale when one of their dependencies (such as bootclasspath jars and
+     * share libraries) has changed. Those stale files are excluded from the statistics. They may be
+     * cleaned up or replaced by ART Service at any time.
+     *
+     * For a preload app, this type includes dexopt artifacts on readonly partitions if they are
+     * up-to-date.
+     */
+    public static final int TYPE_DEXOPT_ARTIFACT = 0;
+    /**
+     * Reference profiles.
+     *
+     * They are the ones used during the last profile-guided dexopt.
+     */
+    public static final int TYPE_REF_PROFILE = 1;
+    /**
+     * Current profiles.
+     *
+     * They may be used during the next profile-guided dexopt.
+     */
+    public static final int TYPE_CUR_PROFILE = 2;
+
+    /** @hide */
+    // clang-format off
+    @IntDef(prefix = {"TYPE_"}, value = {
+        TYPE_DEXOPT_ARTIFACT,
+        TYPE_REF_PROFILE,
+        TYPE_CUR_PROFILE,
+    })
+    // clang-format on
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface FileTypes {}
+
+    /** @hide */
+    protected ArtManagedFileStats() {}
+
+    /** @hide */
+    public static @NonNull ArtManagedFileStats create(
+            long artifactsSize, long refProfilesSize, long curProfilesSize) {
+        return new AutoValue_ArtManagedFileStats(Map.of(TYPE_DEXOPT_ARTIFACT, artifactsSize,
+                TYPE_REF_PROFILE, refProfilesSize, TYPE_CUR_PROFILE, curProfilesSize));
+    }
+
+    /** @hide */
+    public abstract @NonNull Map<Integer, Long> getTotalSizesBytes();
+
+    /**
+     * Returns the total size, in bytes, of the files of the given type.
+     *
+     * @throws IllegalArgumentException if {@code fileType} is not one of those defined in {@link
+     *         FileTypes}.
+     *
+     * @hide
+     */
+    public long getTotalSizeBytesByType(@FileTypes int fileType) {
+        Long value = getTotalSizesBytes().get(fileType);
+        if (value == null) {
+            throw new IllegalArgumentException("Unknown file type " + fileType);
+        }
+        return value;
+    }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
index f1415ab..13c5f79 100644
--- a/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
+++ b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
@@ -61,6 +61,7 @@
 
 import com.android.modules.utils.pm.PackageStateModulesUtils;
 import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.ArtManagedFileStats;
 import com.android.server.art.model.Config;
 import com.android.server.art.model.DeleteResult;
 import com.android.server.art.model.DexoptParams;
@@ -1110,6 +1111,91 @@
                                 PKG_NAME_1, "/data/app/foo/split_0.apk", "arm")));
     }
 
+    @Test
+    public void testGetArtManagedFileStats() throws Exception {
+        // The same setup as `testCleanup`.
+
+        // It should count all artifacts, but not runtime images.
+        doReturn(createGetDexoptStatusResult(
+                         "speed-profile", "bg-dexopt", "location", ArtifactsLocation.NEXT_TO_DEX))
+                .when(mArtd)
+                .getDexoptStatus(eq("/data/app/foo/base.apk"), eq("arm64"), any());
+        doReturn(createGetDexoptStatusResult(
+                         "verify", "cmdline", "location", ArtifactsLocation.NEXT_TO_DEX))
+                .when(mArtd)
+                .getDexoptStatus(eq("/data/user/0/foo/1.apk"), eq("arm64"), any());
+
+        // It should count all artifacts and runtime images.
+        doReturn(createGetDexoptStatusResult(
+                         "verify", "bg-dexopt", "location", ArtifactsLocation.DALVIK_CACHE))
+                .when(mArtd)
+                .getDexoptStatus(eq("/data/app/foo/split_0.apk"), eq("arm64"), any());
+
+        // It should only count VDEX files and runtime images.
+        doReturn(createGetDexoptStatusResult(
+                         "verify", "vdex", "location", ArtifactsLocation.NEXT_TO_DEX))
+                .when(mArtd)
+                .getDexoptStatus(eq("/data/app/foo/split_0.apk"), eq("arm"), any());
+
+        // It should not count any artifacts or runtime images.
+        doReturn(createGetDexoptStatusResult(
+                         "run-from-apk", "unknown", "unknown", ArtifactsLocation.NONE_OR_ERROR))
+                .when(mArtd)
+                .getDexoptStatus(eq("/data/app/foo/base.apk"), eq("arm"), any());
+
+        // These are counted as TYPE_DEXOPT_ARTIFACT.
+        doReturn(1l << 0).when(mArtd).getArtifactsSize(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/app/foo/base.apk", "arm64", false /* isInDalvikCache */)));
+        doReturn(1l << 1).when(mArtd).getArtifactsSize(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/user/0/foo/1.apk", "arm64", false /* isInDalvikCache */)));
+        doReturn(1l << 2).when(mArtd).getArtifactsSize(deepEq(AidlUtils.buildArtifactsPath(
+                "/data/app/foo/split_0.apk", "arm64", true /* isInDalvikCache */)));
+        doReturn(1l << 3).when(mArtd).getVdexFileSize(
+                deepEq(VdexPath.artifactsPath(AidlUtils.buildArtifactsPath(
+                        "/data/app/foo/split_0.apk", "arm", false /* isInDalvikCache */))));
+        doReturn(1l << 4).when(mArtd).getRuntimeArtifactsSize(
+                deepEq(AidlUtils.buildRuntimeArtifactsPath(
+                        PKG_NAME_1, "/data/app/foo/split_0.apk", "arm64")));
+        doReturn(1l << 5).when(mArtd).getRuntimeArtifactsSize(
+                deepEq(AidlUtils.buildRuntimeArtifactsPath(
+                        PKG_NAME_1, "/data/app/foo/split_0.apk", "arm")));
+
+        // These are counted as TYPE_REF_PROFILE.
+        doReturn(1l << 6).when(mArtd).getProfileSize(
+                deepEq(AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME_1, "primary")));
+        doReturn(1l << 7).when(mArtd).getProfileSize(
+                deepEq(AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME_1, "split_0.split")));
+        doReturn(1l << 8).when(mArtd).getProfileSize(
+                deepEq(AidlUtils.buildProfilePathForSecondaryRef("/data/user/0/foo/1.apk")));
+
+        // These are counted as TYPE_CUR_PROFILE.
+        doReturn(1l << 9).when(mArtd).getProfileSize(deepEq(
+                AidlUtils.buildProfilePathForPrimaryCur(0 /* userId */, PKG_NAME_1, "primary")));
+        doReturn(1l << 10).when(mArtd).getProfileSize(deepEq(
+                AidlUtils.buildProfilePathForPrimaryCur(1 /* userId */, PKG_NAME_1, "primary")));
+        doReturn(1l << 11).when(mArtd).getProfileSize(
+                deepEq(AidlUtils.buildProfilePathForPrimaryCur(
+                        0 /* userId */, PKG_NAME_1, "split_0.split")));
+        doReturn(1l << 12).when(mArtd).getProfileSize(
+                deepEq(AidlUtils.buildProfilePathForPrimaryCur(
+                        1 /* userId */, PKG_NAME_1, "split_0.split")));
+        doReturn(1l << 13).when(mArtd).getProfileSize(
+                deepEq(AidlUtils.buildProfilePathForSecondaryCur("/data/user/0/foo/1.apk")));
+
+        ArtManagedFileStats stats = mArtManagerLocal.getArtManagedFileStats(mSnapshot, PKG_NAME_1);
+        assertThat(stats.getTotalSizeBytesByType(ArtManagedFileStats.TYPE_DEXOPT_ARTIFACT))
+                .isEqualTo((1l << 0) + (1l << 1) + (1l << 2) + (1l << 3) + (1l << 4) + (1l << 5));
+        assertThat(stats.getTotalSizeBytesByType(ArtManagedFileStats.TYPE_REF_PROFILE))
+                .isEqualTo((1l << 6) + (1l << 7) + (1l << 8));
+        assertThat(stats.getTotalSizeBytesByType(ArtManagedFileStats.TYPE_CUR_PROFILE))
+                .isEqualTo((1l << 9) + (1l << 10) + (1l << 11) + (1l << 12) + (1l << 13));
+
+        verify(mArtd, times(3)).getArtifactsSize(any());
+        verify(mArtd, times(1)).getVdexFileSize(any());
+        verify(mArtd, times(2)).getRuntimeArtifactsSize(any());
+        verify(mArtd, times(8)).getProfileSize(any());
+    }
+
     private AndroidPackage createPackage(boolean multiSplit) {
         AndroidPackage pkg = mock(AndroidPackage.class);