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);