ART services: get optimization status.
- Implements the functionality of getting the optimization status of
primary dex'es (except the real artd implementation).
- Defines an artd internal API for getting the optimization status.
- Adds a shell command (`get-optimization-status`).
Bug: 233383589
Test: atest ArtServiceTests
Test: adb shell pm art get-optimization-status com.google.android.youtube
Ignore-AOSP-First: ART Services
Change-Id: I4128a2c8a618c348954a81b22c21f0bd0944496d
diff --git a/artd/artd.cc b/artd/artd.cc
index 16127b0..89b9421 100644
--- a/artd/artd.cc
+++ b/artd/artd.cc
@@ -33,6 +33,7 @@
using ::aidl::com::android::server::art::ArtifactsPath;
using ::aidl::com::android::server::art::BnArtd;
+using ::aidl::com::android::server::art::GetOptimizationStatusResult;
using ::android::base::Error;
using ::android::base::Result;
using ::ndk::ScopedAStatus;
@@ -55,6 +56,17 @@
return ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
}
+ ScopedAStatus getOptimizationStatus(const std::string& in_dexFile,
+ const std::string& in_instructionSet,
+ const std::string& in_classLoaderContext,
+ GetOptimizationStatusResult* _aidl_return) override {
+ (void)in_dexFile;
+ (void)in_instructionSet;
+ (void)in_classLoaderContext;
+ (void)_aidl_return;
+ return ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+ }
+
Result<void> Start() {
LOG(INFO) << "Starting artd";
diff --git a/artd/binder/com/android/server/art/GetOptimizationStatusResult.aidl b/artd/binder/com/android/server/art/GetOptimizationStatusResult.aidl
new file mode 100644
index 0000000..99a2e37
--- /dev/null
+++ b/artd/binder/com/android/server/art/GetOptimizationStatusResult.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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.getOptimizationStatus}. Each field corresponds to a field in
+ * {@code com.android.server.art.model.OptimizationStatus.DexFileOptimizationStatus}.
+ *
+ * @hide
+ */
+parcelable GetOptimizationStatusResult {
+ @utf8InCpp String compilerFilter;
+ @utf8InCpp String compilationReason;
+ @utf8InCpp String locationDebugString;
+}
diff --git a/artd/binder/com/android/server/art/IArtd.aidl b/artd/binder/com/android/server/art/IArtd.aidl
index 8fc1660..a1df266 100644
--- a/artd/binder/com/android/server/art/IArtd.aidl
+++ b/artd/binder/com/android/server/art/IArtd.aidl
@@ -23,4 +23,9 @@
/** Deletes artifacts and returns the released space, in bytes. */
long deleteArtifacts(in com.android.server.art.ArtifactsPath artifactsPath);
+
+ /** Returns the optimization status of a dex file. */
+ com.android.server.art.GetOptimizationStatusResult getOptimizationStatus(
+ @utf8InCpp String dexFile, @utf8InCpp String instructionSet,
+ @utf8InCpp String classLoaderContext);
}
diff --git a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
index 3a50d3d..19e529a 100644
--- a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
+++ b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
@@ -16,7 +16,9 @@
package com.android.server.art;
+import static com.android.server.art.PrimaryDexUtils.DetailedPrimaryDexInfo;
import static com.android.server.art.PrimaryDexUtils.PrimaryDexInfo;
+import static com.android.server.art.model.OptimizationStatus.DexFileOptimizationStatus;
import android.annotation.NonNull;
import android.annotation.SystemApi;
@@ -28,12 +30,16 @@
import com.android.server.art.IArtd;
import com.android.server.art.model.DeleteOptions;
import com.android.server.art.model.DeleteResult;
+import com.android.server.art.model.GetStatusOptions;
+import com.android.server.art.model.OptimizationStatus;
import com.android.server.art.wrapper.AndroidPackageApi;
import com.android.server.art.wrapper.PackageDataSnapshot;
import com.android.server.art.wrapper.PackageManagerLocal;
import com.android.server.art.wrapper.PackageState;
import java.io.FileDescriptor;
+import java.util.ArrayList;
+import java.util.List;
/**
* This class provides a system API for functionality provided by the ART module.
@@ -97,15 +103,8 @@
throw new IllegalArgumentException("Nothing to delete");
}
- PackageState pkgState = mInjector.getPackageManagerLocal().getPackageState(
- snapshot, Binder.getCallingUid(), packageName);
- if (pkgState == null) {
- throw new IllegalArgumentException("Package not found: " + packageName);
- }
- AndroidPackageApi pkg = pkgState.getAndroidPackage();
- if (pkg == null) {
- throw new IllegalStateException("Unable to get package " + pkgState.getPackageName());
- }
+ PackageState pkgState = getPackageStateOrThrow(snapshot, packageName);
+ AndroidPackageApi pkg = getPackageOrThrow(pkgState);
try {
long freedBytes = 0;
@@ -136,6 +135,74 @@
}
/**
+ * Returns the optimization status of a package.
+ *
+ * @throws IllegalArgumentException if the package is not found or the options are illegal
+ * @throws IllegalStateException if an internal error occurs
+ *
+ * @hide
+ */
+ @NonNull
+ public OptimizationStatus getOptimizationStatus(@NonNull PackageDataSnapshot snapshot,
+ @NonNull String packageName, @NonNull GetStatusOptions options) {
+ if (!options.isForPrimaryDex() && !options.isForSecondaryDex()) {
+ throw new IllegalArgumentException("Nothing to check");
+ }
+
+ PackageState pkgState = getPackageStateOrThrow(snapshot, packageName);
+ AndroidPackageApi pkg = getPackageOrThrow(pkgState);
+
+ try {
+ List<DexFileOptimizationStatus> statuses = new ArrayList<>();
+
+ if (options.isForPrimaryDex()) {
+ for (DetailedPrimaryDexInfo dexInfo :
+ PrimaryDexUtils.getDetailedDexInfo(pkgState, pkg)) {
+ if (!dexInfo.hasCode()) {
+ continue;
+ }
+ for (String isa : Utils.getAllIsas(pkgState)) {
+ GetOptimizationStatusResult result =
+ mInjector.getArtd().getOptimizationStatus(
+ dexInfo.dexPath(), isa, dexInfo.classLoaderContext());
+ statuses.add(new DexFileOptimizationStatus(dexInfo.dexPath(), isa,
+ result.compilerFilter, result.compilationReason,
+ result.locationDebugString));
+ }
+ }
+ }
+
+ if (options.isForSecondaryDex()) {
+ // TODO(jiakaiz): Implement this.
+ throw new UnsupportedOperationException(
+ "Getting optimization status of secondary dex'es is not implemented yet");
+ }
+
+ return new OptimizationStatus(statuses);
+ } catch (RemoteException e) {
+ throw new IllegalStateException("An error occurred when calling artd", e);
+ }
+ }
+
+ private PackageState getPackageStateOrThrow(
+ @NonNull PackageDataSnapshot snapshot, @NonNull String packageName) {
+ PackageState pkgState = mInjector.getPackageManagerLocal().getPackageState(
+ snapshot, Binder.getCallingUid(), packageName);
+ if (pkgState == null) {
+ throw new IllegalArgumentException("Package not found: " + packageName);
+ }
+ return pkgState;
+ }
+
+ private AndroidPackageApi getPackageOrThrow(@NonNull PackageState pkgState) {
+ AndroidPackageApi pkg = pkgState.getAndroidPackage();
+ if (pkg == null) {
+ throw new IllegalStateException("Unable to get package " + pkgState.getPackageName());
+ }
+ return pkg;
+ }
+
+ /**
* Injector pattern for testing purpose.
*
* @hide
diff --git a/libartservice/service/java/com/android/server/art/ArtShellCommand.java b/libartservice/service/java/com/android/server/art/ArtShellCommand.java
index ab8330f..ba01ed5 100644
--- a/libartservice/service/java/com/android/server/art/ArtShellCommand.java
+++ b/libartservice/service/java/com/android/server/art/ArtShellCommand.java
@@ -16,12 +16,16 @@
package com.android.server.art;
+import static com.android.server.art.model.OptimizationStatus.DexFileOptimizationStatus;
+
import android.os.Binder;
import android.os.Process;
import com.android.modules.utils.BasicShellCommandHandler;
import com.android.server.art.model.DeleteOptions;
import com.android.server.art.model.DeleteResult;
+import com.android.server.art.model.GetStatusOptions;
+import com.android.server.art.model.OptimizationStatus;
import com.android.server.art.wrapper.PackageDataSnapshot;
import com.android.server.art.wrapper.PackageManagerLocal;
@@ -55,6 +59,18 @@
snapshot, getNextArgRequired(), new DeleteOptions.Builder().build());
pw.printf("Freed %d bytes\n", result.getFreedBytes());
return 0;
+ case "get-optimization-status":
+ OptimizationStatus optimizationStatus = mArtManagerLocal.getOptimizationStatus(
+ snapshot, getNextArgRequired(), new GetStatusOptions.Builder().build());
+ for (DexFileOptimizationStatus status :
+ optimizationStatus.getDexFileOptimizationStatuses()) {
+ pw.printf("dexFile = %s, instructionSet = %s, compilerFilter = %s, "
+ + "compilationReason = %s, locationDebugString = %s\n",
+ status.getDexFile(), status.getInstructionSet(),
+ status.getCompilerFilter(), status.getCompilationReason(),
+ status.getLocationDebugString());
+ }
+ return 0;
default:
// Handles empty, help, and invalid commands.
return handleDefaultCommands(cmd);
@@ -78,6 +94,10 @@
pw.println(" Delete the optimized artifacts of a package.");
pw.println(" By default, the command only deletes the optimized artifacts of primary "
+ "dex'es.");
+ pw.println(" get-optimization-status <package-name>");
+ pw.println(" Print the optimization status of a package.");
+ pw.println(" By default, the command only prints the optimization status of primary "
+ + "dex'es.");
}
private void enforceRoot() {
diff --git a/libartservice/service/java/com/android/server/art/LoggingArtd.java b/libartservice/service/java/com/android/server/art/LoggingArtd.java
index a29144a..811cb6f 100644
--- a/libartservice/service/java/com/android/server/art/LoggingArtd.java
+++ b/libartservice/service/java/com/android/server/art/LoggingArtd.java
@@ -43,6 +43,15 @@
return 0;
}
+ @Override
+ public GetOptimizationStatusResult getOptimizationStatus(
+ String dexFile, String instructionSet, String classLoaderContext) {
+ Log.i(TAG,
+ "getOptimizationStatus " + dexFile + ", " + instructionSet + ", "
+ + classLoaderContext);
+ return new GetOptimizationStatusResult();
+ }
+
private String artifactsPathToString(ArtifactsPath artifactsPath) {
return String.format("ArtifactsPath{dexPath = \"%s\", isa = \"%s\", isInDalvikCache = %s}",
artifactsPath.dexPath, artifactsPath.isa,
diff --git a/libartservice/service/java/com/android/server/art/model/GetStatusOptions.java b/libartservice/service/java/com/android/server/art/model/GetStatusOptions.java
new file mode 100644
index 0000000..22b0131
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/model/GetStatusOptions.java
@@ -0,0 +1,56 @@
+/*
+ * 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.model;
+
+/** @hide */
+public class GetStatusOptions {
+ public static final class Builder {
+ private GetStatusOptions mGetStatusOptions = new GetStatusOptions();
+
+ /** Whether to get optimization status of primary dex'es. Default: true. */
+ public Builder setForPrimaryDex(boolean value) {
+ mGetStatusOptions.mIsForPrimaryDex = value;
+ return this;
+ }
+
+ /** Whether to get optimization status of secondary dex'es. Default: false. */
+ public Builder setForSecondaryDex(boolean value) {
+ mGetStatusOptions.mIsForSecondaryDex = value;
+ return this;
+ }
+
+ /** Returns the built object. */
+ public GetStatusOptions build() {
+ return mGetStatusOptions;
+ }
+ }
+
+ private boolean mIsForPrimaryDex = true;
+ private boolean mIsForSecondaryDex = false;
+
+ private GetStatusOptions() {}
+
+ /** Whether to get optimization status of primary dex'es. */
+ public boolean isForPrimaryDex() {
+ return mIsForPrimaryDex;
+ }
+
+ /** Whether to get optimization status of secondary dex'es. */
+ public boolean isForSecondaryDex() {
+ return mIsForSecondaryDex;
+ }
+}
diff --git a/libartservice/service/java/com/android/server/art/model/OptimizationStatus.java b/libartservice/service/java/com/android/server/art/model/OptimizationStatus.java
new file mode 100644
index 0000000..4b6bae2
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/model/OptimizationStatus.java
@@ -0,0 +1,118 @@
+/*
+ * 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.model;
+
+import android.annotation.NonNull;
+
+import com.android.internal.annotations.Immutable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Describes the optimization status of a package.
+ *
+ * @hide
+ */
+@Immutable
+public class OptimizationStatus {
+ private final @NonNull List<DexFileOptimizationStatus> mDexFileOptimizationStatuses;
+
+ /** @hide */
+ public OptimizationStatus(
+ @NonNull List<DexFileOptimizationStatus> dexFileOptimizationStatuses) {
+ mDexFileOptimizationStatuses = dexFileOptimizationStatuses;
+ }
+
+ /** The optimization status of each individual dex file. */
+ @NonNull
+ public List<DexFileOptimizationStatus> getDexFileOptimizationStatuses() {
+ return mDexFileOptimizationStatuses;
+ }
+
+ /** Describes the optimization status of a dex file. */
+ @Immutable
+ public static class DexFileOptimizationStatus {
+ private final @NonNull String mDexFile;
+ private final @NonNull String mInstructionSet;
+ private final @NonNull String mCompilerFilter;
+ private final @NonNull String mCompilationReason;
+ private final @NonNull String mLocationDebugString;
+
+ /** @hide */
+ public DexFileOptimizationStatus(@NonNull String dexFile, @NonNull String instructionSet,
+ @NonNull String compilerFilter, @NonNull String compilationReason,
+ @NonNull String locationDebugString) {
+ mDexFile = dexFile;
+ mInstructionSet = instructionSet;
+ mCompilerFilter = compilerFilter;
+ mCompilationReason = compilationReason;
+ mLocationDebugString = locationDebugString;
+ }
+
+ /** The absolute path to the dex file. */
+ public @NonNull String getDexFile() {
+ return mDexFile;
+ }
+
+ /** The instruction set. */
+ public @NonNull String getInstructionSet() {
+ return mInstructionSet;
+ }
+
+ /**
+ * A string that describes the compiler filter.
+ *
+ * Possible values are:
+ * <ul>
+ * <li>A valid value of the {@code --compiler-filer} option passed to {@code dex2oat}, if
+ * the optimized artifacts are valid.
+ * <li>{@code "run-from-apk"}, if the optimized artifacts do not exist.
+ * <li>{@code "run-from-apk-fallback"}, if the optimized artifacts exist but are invalid
+ * because the dex file has changed.
+ * <li>{@code "error"}, if an unexpected error occurs.
+ * </ul>
+ */
+ public @NonNull String getCompilerFilter() {
+ return mCompilerFilter;
+ }
+
+ /**
+ * A string that describes the compilation reason.
+ *
+ * Possible values are:
+ * <ul>
+ * <li>The compilation reason, in text format, passed to {@code dex2oat}.
+ * <li>{@code "unknown"}: if the reason is empty or the optimized artifacts do not exist.
+ * <li>{@code "error"}: if an unexpected error occurs.
+ * </ul>
+ */
+ public @NonNull String getCompilationReason() {
+ return mCompilationReason;
+ }
+
+ /**
+ * A human-readable string that describes the location of the optimized artifacts.
+ *
+ * Note that this string is for debugging purposes only. There is no stability guarantees
+ * for the format of the string. DO NOT use it programmatically.
+ */
+ public @NonNull String getLocationDebugString() {
+ return mLocationDebugString;
+ }
+ }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
index 249de3c..bbfa5f9 100644
--- a/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
+++ b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
@@ -16,6 +16,8 @@
package com.android.server.art;
+import static com.android.server.art.model.OptimizationStatus.DexFileOptimizationStatus;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.any;
@@ -34,6 +36,8 @@
import com.android.server.art.model.DeleteOptions;
import com.android.server.art.model.DeleteResult;
+import com.android.server.art.model.GetStatusOptions;
+import com.android.server.art.model.OptimizationStatus;
import com.android.server.art.wrapper.AndroidPackageApi;
import com.android.server.art.wrapper.PackageDataSnapshot;
import com.android.server.art.wrapper.PackageManagerLocal;
@@ -131,6 +135,65 @@
mock(PackageDataSnapshot.class), PKG_NAME, new DeleteOptions.Builder().build());
}
+ @Test
+ public void testGetOptimizationStatus() throws Exception {
+ when(mArtd.getOptimizationStatus(any(), any(), any()))
+ .thenReturn(createGetOptimizationStatusResult(
+ "speed", "compilation-reason-0", "location-debug-string-0"),
+ createGetOptimizationStatusResult(
+ "speed-profile", "compilation-reason-1", "location-debug-string-1"),
+ createGetOptimizationStatusResult(
+ "verify", "compilation-reason-2", "location-debug-string-2"),
+ createGetOptimizationStatusResult(
+ "extract", "compilation-reason-3", "location-debug-string-3"));
+
+ OptimizationStatus result = mArtManagerLocal.getOptimizationStatus(
+ mock(PackageDataSnapshot.class), PKG_NAME, new GetStatusOptions.Builder().build());
+
+ List<DexFileOptimizationStatus> statuses = result.getDexFileOptimizationStatuses();
+ assertThat(statuses.size()).isEqualTo(4);
+
+ assertThat(statuses.get(0).getDexFile()).isEqualTo("/data/app/foo/base.apk");
+ assertThat(statuses.get(0).getInstructionSet()).isEqualTo("arm64");
+ assertThat(statuses.get(0).getCompilerFilter()).isEqualTo("speed");
+ assertThat(statuses.get(0).getCompilationReason()).isEqualTo("compilation-reason-0");
+ assertThat(statuses.get(0).getLocationDebugString()).isEqualTo("location-debug-string-0");
+
+ assertThat(statuses.get(1).getDexFile()).isEqualTo("/data/app/foo/base.apk");
+ assertThat(statuses.get(1).getInstructionSet()).isEqualTo("arm");
+ assertThat(statuses.get(1).getCompilerFilter()).isEqualTo("speed-profile");
+ assertThat(statuses.get(1).getCompilationReason()).isEqualTo("compilation-reason-1");
+ assertThat(statuses.get(1).getLocationDebugString()).isEqualTo("location-debug-string-1");
+
+ assertThat(statuses.get(2).getDexFile()).isEqualTo("/data/app/foo/split_0.apk");
+ assertThat(statuses.get(2).getInstructionSet()).isEqualTo("arm64");
+ assertThat(statuses.get(2).getCompilerFilter()).isEqualTo("verify");
+ assertThat(statuses.get(2).getCompilationReason()).isEqualTo("compilation-reason-2");
+ assertThat(statuses.get(2).getLocationDebugString()).isEqualTo("location-debug-string-2");
+
+ assertThat(statuses.get(3).getDexFile()).isEqualTo("/data/app/foo/split_0.apk");
+ assertThat(statuses.get(3).getInstructionSet()).isEqualTo("arm");
+ assertThat(statuses.get(3).getCompilerFilter()).isEqualTo("extract");
+ assertThat(statuses.get(3).getCompilationReason()).isEqualTo("compilation-reason-3");
+ assertThat(statuses.get(3).getLocationDebugString()).isEqualTo("location-debug-string-3");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testGetOptimizationStatusPackageNotFound() throws Exception {
+ when(mPackageManagerLocal.getPackageState(any(), anyInt(), eq(PKG_NAME))).thenReturn(null);
+
+ mArtManagerLocal.getOptimizationStatus(
+ mock(PackageDataSnapshot.class), PKG_NAME, new GetStatusOptions.Builder().build());
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testGetOptimizationStatusNoPackage() throws Exception {
+ when(mPkgState.getAndroidPackage()).thenReturn(null);
+
+ mArtManagerLocal.getOptimizationStatus(
+ mock(PackageDataSnapshot.class), PKG_NAME, new GetStatusOptions.Builder().build());
+ }
+
private AndroidPackageApi createPackage() {
AndroidPackageApi pkg = mock(AndroidPackageApi.class);
@@ -163,4 +226,13 @@
return pkgState;
}
+
+ private GetOptimizationStatusResult createGetOptimizationStatusResult(
+ String compilerFilter, String compilationReason, String locationDebugString) {
+ var getOptimizationStatusResult = new GetOptimizationStatusResult();
+ getOptimizationStatusResult.compilerFilter = compilerFilter;
+ getOptimizationStatusResult.compilationReason = compilationReason;
+ getOptimizationStatusResult.locationDebugString = locationDebugString;
+ return getOptimizationStatusResult;
+ }
}