diff options
author | 2024-09-09 11:11:30 +0000 | |
---|---|---|
committer | 2024-09-20 10:20:01 +0000 | |
commit | 8a5ecf2f6734241f511a088a2a474674373924e7 (patch) | |
tree | 6241326480afab7db515b8af79f44adb8dbd9918 | |
parent | a303efff1731e5b7e9574a4e54701515a4c16d41 (diff) |
Report dex2oat metrics to StatsD when dexopting
Bug: 327134025
Test: atest art_standalone_artd_tests && atest ArtServicesTest
Change-Id: I4212899e78c23d8ff8c0307f9699f6b772c50000
19 files changed, 394 insertions, 56 deletions
diff --git a/artd/artd.cc b/artd/artd.cc index 44f11f366d..493cc10926 100644 --- a/artd/artd.cc +++ b/artd/artd.cc @@ -1171,22 +1171,33 @@ ndk::ScopedAStatus Artd::dexopt( << "\nOpened FDs: " << fd_logger; ProcessStat stat; - Result<int> result = ExecAndReturnCode( - art_exec_args.Get(), kLongTimeoutSec, cancellation_signal->CreateExecCallbacks(), &stat); + std::string error_msg; + ExecResult result = exec_utils_->ExecAndReturnResult(art_exec_args.Get(), + kLongTimeoutSec, + cancellation_signal->CreateExecCallbacks(), + /*new_process_group=*/true, + &stat, + &error_msg); _aidl_return->wallTimeMs = stat.wall_time_ms; _aidl_return->cpuTimeMs = stat.cpu_time_ms; - if (!result.ok()) { + + auto result_info = ART_FORMAT("[status={},exit_code={},signal={}]", + static_cast<int>(result.status), + result.exit_code, + result.signal); + if (result.status != ExecResult::kExited) { if (cancellation_signal->IsCancelled()) { _aidl_return->cancelled = true; return ScopedAStatus::ok(); } - return NonFatal("Failed to run dex2oat: " + result.error().message()); + return NonFatal(ART_FORMAT("Failed to run dex2oat: {} {}", error_msg, result_info)); } - LOG(INFO) << ART_FORMAT("dex2oat returned code {}", result.value()); + LOG(INFO) << ART_FORMAT("dex2oat returned code {}", result.exit_code); - if (result.value() != 0) { - return NonFatal(ART_FORMAT("dex2oat returned an unexpected code: {}", result.value())); + if (result.exit_code != 0) { + return NonFatal( + ART_FORMAT("dex2oat returned an unexpected code: {} {}", result.exit_code, result_info)); } int64_t size_bytes = 0; diff --git a/artd/artd_test.cc b/artd/artd_test.cc index 1a53df714f..34950eee74 100644 --- a/artd/artd_test.cc +++ b/artd/artd_test.cc @@ -313,8 +313,9 @@ class MockExecUtils : public ExecUtils { Result<int> code = DoExecAndReturnCode(arg_vector, callbacks, stat); if (code.ok()) { return {.status = ExecResult::kExited, .exit_code = code.value()}; + } else { + return {.status = ExecResult::kSignaled, .signal = SIGKILL}; } - return {.status = ExecResult::kUnknown}; } MOCK_METHOD(Result<int>, @@ -1113,14 +1114,29 @@ TEST_F(ArtdTest, dexoptAllResourceControlBackground) { RunDexopt(); } +TEST_F(ArtdTest, dexoptTerminatedBySignal) { + EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)) + .WillOnce(Return(Result<int>(Error()))); + RunDexopt(AllOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_SERVICE_SPECIFIC), + Property(&ndk::ScopedAStatus::getMessage, + HasSubstr(ART_FORMAT("[status={},exit_code=-1,signal={}]", + static_cast<int>(ExecResult::kSignaled), + SIGKILL))))); +} + TEST_F(ArtdTest, dexoptFailed) { dexopt_options_.generateAppImage = true; + constexpr int kExitCode = 135; EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)) .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--oat-fd=", "new_oat")), WithArg<0>(WriteToFdFlag("--output-vdex-fd=", "new_vdex")), WithArg<0>(WriteToFdFlag("--app-image-fd=", "new_art")), - Return(1))); - RunDexopt(EX_SERVICE_SPECIFIC); + Return(kExitCode))); + RunDexopt(AllOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_SERVICE_SPECIFIC), + Property(&ndk::ScopedAStatus::getMessage, + HasSubstr(ART_FORMAT("[status={},exit_code={},signal=0]", + static_cast<int>(ExecResult::kExited), + kExitCode))))); CheckContent(scratch_path_ + "/a/oat/arm64/b.odex", "old_oat"); CheckContent(scratch_path_ + "/a/oat/arm64/b.vdex", "old_vdex"); diff --git a/artd/binder/com/android/server/art/IArtd.aidl b/artd/binder/com/android/server/art/IArtd.aidl index 47fc89966b..75b634dffa 100644 --- a/artd/binder/com/android/server/art/IArtd.aidl +++ b/artd/binder/com/android/server/art/IArtd.aidl @@ -155,7 +155,19 @@ interface IArtd { /** * Dexopts a dex file for the given instruction set. * - * Throws fatal and non-fatal errors. + * Throws fatal and non-fatal errors. When dexopt fails, the non-fatal status includes an error + * message containing a substring formatted as: + * [status=%STATUS%,exit_code=%EXIT_CODE%,signal=%SIGNAL%] + * where %STATUS% is the integer value of the corresponding ExecResultStatus enumeration defined + * in frameworks/proto_logging/stats/enums/art/common_enums.proto, + * %EXIT_CODE% is the exit code for the dex2oat process and set only when %STATUS% is set to + * EXEC_RESULT_STATUS_EXITED (-1 otherwsie), + * and %SIGNAL% is the signal that interrupted the dex2oat + * process and set only when %STATUS% is EXEC_RESULT_STATUS_SIGNALED (0 otherwise). + * + * The purpose of this format is to share information about the dex2oat run result with the + * ArtService code in Java that orchestrates the dexopt process, so that it can be reported to + * StatsD. */ com.android.server.art.ArtdDexoptResult dexopt( in com.android.server.art.OutputArtifacts outputArtifacts, diff --git a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java index 61d3b159c7..8436fd1095 100644 --- a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java +++ b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java @@ -20,7 +20,6 @@ import static com.android.server.art.ArtFileManager.ProfileLists; import static com.android.server.art.ArtFileManager.UsableArtifactLists; import static com.android.server.art.ArtFileManager.WritableArtifactLists; import static com.android.server.art.DexMetadataHelper.DexMetadataInfo; -import static com.android.server.art.DexUseManagerLocal.SecondaryDexInfo; import static com.android.server.art.PrimaryDexUtils.DetailedPrimaryDexInfo; import static com.android.server.art.PrimaryDexUtils.PrimaryDexInfo; import static com.android.server.art.ProfilePath.WritableProfilePath; @@ -102,6 +101,8 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Consumer; @@ -1521,6 +1522,7 @@ public final class ArtManagerLocal { @Nullable private final Context mContext; @Nullable private final PackageManagerLocal mPackageManagerLocal; @Nullable private final Config mConfig; + @Nullable private final ThreadPoolExecutor mReporterExecutor; @Nullable private BackgroundDexoptJob mBgDexoptJob = null; @Nullable private PreRebootDexoptJob mPrDexoptJob = null; @@ -1531,6 +1533,7 @@ public final class ArtManagerLocal { mContext = null; mPackageManagerLocal = null; mConfig = null; + mReporterExecutor = null; } @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @@ -1541,6 +1544,10 @@ public final class ArtManagerLocal { mPackageManagerLocal = Objects.requireNonNull( LocalManagerRegistry.getManager(PackageManagerLocal.class)); mConfig = new Config(); + mReporterExecutor = + new ThreadPoolExecutor(1 /* corePoolSize */, 1 /* maximumPoolSize */, + 60 /* keepTimeAlive */, TimeUnit.SECONDS, new LinkedBlockingQueue<>()); + mReporterExecutor.allowsCoreThreadTimeOut(); // Call the getters for the dependencies that aren't optional, to ensure correct // initialization order. @@ -1587,7 +1594,7 @@ public final class ArtManagerLocal { @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @NonNull public DexoptHelper getDexoptHelper() { - return new DexoptHelper(getContext(), getConfig()); + return new DexoptHelper(getContext(), getConfig(), getReporterExecutor()); } @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @@ -1596,6 +1603,12 @@ public final class ArtManagerLocal { return mConfig; } + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @NonNull + public Executor getReporterExecutor() { + return mReporterExecutor; + } + /** Returns the registered {@link AppHibernationManager} instance. */ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @NonNull diff --git a/libartservice/service/java/com/android/server/art/Dex2OatStatsReporter.java b/libartservice/service/java/com/android/server/art/Dex2OatStatsReporter.java new file mode 100644 index 0000000000..d553113301 --- /dev/null +++ b/libartservice/service/java/com/android/server/art/Dex2OatStatsReporter.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2024 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; + +import android.os.Build; + +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; + +import com.android.server.art.model.DetailedDexInfo; +import com.android.server.art.model.DexMetadata; + +/** + * A class to report dex2oat metrics to StatsD. + * + * @hide + */ +@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) +public class Dex2OatStatsReporter { + public static void report(int appId, @NonNull String compilerFilter, + @NonNull String compilationReason, @DexMetadata.Type int dexMetadataType, + @NonNull DetailedDexInfo dexInfo, @NonNull String isa, @NonNull Dex2OatResult result, + long artifactsSize, long compilationTime) { + ArtStatsLog.write(ArtStatsLog.ART_DEX2OAT_REPORTED, appId, + translateCompilerFilter(compilerFilter), + translateCompilationReason(compilationReason), + translateDexMetadataType(dexMetadataType), getApkType(dexInfo), translateIsa(isa), + result.status, result.exitCode, result.signal, (int) (artifactsSize / 1024), + (int) compilationTime); + } + + private static int translateCompilerFilter(String compilerFilter) { + return switch (compilerFilter) { + case "assume-verified" -> + ArtStatsLog + .ART_DEX2_OAT_REPORTED__COMPILER_FILTER__ART_COMPILATION_FILTER_ASSUMED_VERIFIED; + case "verify" -> + ArtStatsLog.ART_DEX2_OAT_REPORTED__COMPILER_FILTER__ART_COMPILATION_FILTER_VERIFY; + case "space-profile" -> + ArtStatsLog + .ART_DEX2_OAT_REPORTED__COMPILER_FILTER__ART_COMPILATION_FILTER_SPACE_PROFILE; + case "space" -> + ArtStatsLog.ART_DEX2_OAT_REPORTED__COMPILER_FILTER__ART_COMPILATION_FILTER_SPACE; + case "speed-profile" -> + ArtStatsLog + .ART_DEX2_OAT_REPORTED__COMPILER_FILTER__ART_COMPILATION_FILTER_SPEED_PROFILE; + case "speed" -> + ArtStatsLog.ART_DEX2_OAT_REPORTED__COMPILER_FILTER__ART_COMPILATION_FILTER_SPEED; + case "everything-profile" -> + ArtStatsLog + .ART_DEX2_OAT_REPORTED__COMPILER_FILTER__ART_COMPILATION_FILTER_EVERYTHING_PROFILE; + case "everything" -> + ArtStatsLog + .ART_DEX2_OAT_REPORTED__COMPILER_FILTER__ART_COMPILATION_FILTER_EVERYTHING; + default -> + ArtStatsLog.ART_DEX2_OAT_REPORTED__COMPILER_FILTER__ART_COMPILATION_FILTER_UNKNOWN; + }; + } + + private static int translateCompilationReason(String compilationReason) { + return switch (compilationReason) { + case ReasonMapping.REASON_FIRST_BOOT -> + ArtStatsLog + .ART_DEX2_OAT_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_FIRST_BOOT; + case ReasonMapping.REASON_BOOT_AFTER_OTA -> + ArtStatsLog + .ART_DEX2_OAT_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_BOOT_AFTER_OTA; + case ReasonMapping.REASON_BOOT_AFTER_MAINLINE_UPDATE -> + ArtStatsLog + .ART_DEX2_OAT_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_BOOT_AFTER_MAINLINE_UPDATE; + case ReasonMapping.REASON_INSTALL -> + ArtStatsLog + .ART_DEX2_OAT_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_INSTALL; + case ReasonMapping.REASON_BG_DEXOPT -> + ArtStatsLog + .ART_DEX2_OAT_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_BG_DEXOPT; + case ReasonMapping.REASON_CMDLINE -> + ArtStatsLog + .ART_DEX2_OAT_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_CMDLINE; + case ReasonMapping.REASON_INACTIVE -> + ArtStatsLog + .ART_DEX2_OAT_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_INACTIVE; + case ReasonMapping.REASON_PRE_REBOOT_DEXOPT -> + ArtStatsLog + .ART_DEX2_OAT_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_AB_OTA; + case ReasonMapping.REASON_INSTALL_FAST -> + ArtStatsLog + .ART_DEX2_OAT_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_INSTALL_FAST; + case ReasonMapping.REASON_INSTALL_BULK -> + ArtStatsLog + .ART_DEX2_OAT_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_INSTALL_BULK; + case ReasonMapping.REASON_INSTALL_BULK_SECONDARY -> + ArtStatsLog + .ART_DEX2_OAT_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_INSTALL_BULK_SECONDARY; + case ReasonMapping.REASON_INSTALL_BULK_DOWNGRADED -> + ArtStatsLog + .ART_DEX2_OAT_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_INSTALL_BULK_DOWNGRADED; + case ReasonMapping.REASON_INSTALL_BULK_SECONDARY_DOWNGRADED -> + ArtStatsLog + .ART_DEX2_OAT_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_INSTALL_BULK_SECONDARY_DOWNGRADED; + default -> + ArtStatsLog + .ART_DEX2_OAT_REPORTED__COMPILATION_REASON__ART_COMPILATION_REASON_UNKNOWN; + }; + } + + private static int translateIsa(String isa) { + return switch (isa) { + case "arm" -> ArtStatsLog.ART_DEX2_OAT_REPORTED__ISA__ART_ISA_ARM; + case "arm64" -> ArtStatsLog.ART_DEX2_OAT_REPORTED__ISA__ART_ISA_ARM64; + case "riscv64" -> ArtStatsLog.ART_DEX2_OAT_REPORTED__ISA__ART_ISA_RISCV64; + case "x86" -> ArtStatsLog.ART_DEX2_OAT_REPORTED__ISA__ART_ISA_X86; + case "x86_64" -> ArtStatsLog.ART_DATUM_DELTA_REPORTED__ISA__ART_ISA_X86_64; + default -> ArtStatsLog.ART_DEX2_OAT_REPORTED__ISA__ART_ISA_UNKNOWN; + }; + } + + private static int translateDexMetadataType(@DexMetadata.Type int dexMetadataType) { + return switch (dexMetadataType) { + case DexMetadata.TYPE_PROFILE -> + ArtStatsLog.ART_DEX2_OAT_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_PROFILE; + case DexMetadata.TYPE_VDEX -> + ArtStatsLog.ART_DEX2_OAT_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_VDEX; + case DexMetadata.TYPE_PROFILE_AND_VDEX -> + ArtStatsLog + .ART_DEX2_OAT_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_PROFILE_AND_VDEX; + case DexMetadata.TYPE_NONE -> + ArtStatsLog.ART_DEX2_OAT_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_NONE; + case DexMetadata.TYPE_ERROR -> + ArtStatsLog.ART_DEX2_OAT_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_ERROR; + case DexMetadata.TYPE_UNKNOWN -> + ArtStatsLog.ART_DEX2_OAT_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_UNKNOWN; + default -> + ArtStatsLog.ART_DEX2_OAT_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_UNKNOWN; + }; + } + + private static int getApkType(DetailedDexInfo dexInfo) { + if (dexInfo instanceof PrimaryDexUtils.PrimaryDexInfo primaryDexInfo) { + return primaryDexInfo.splitName() == null + ? ArtStatsLog.ART_DEX2_OAT_REPORTED__APK_TYPE__ART_APK_TYPE_BASE + : ArtStatsLog.ART_DEX2_OAT_REPORTED__APK_TYPE__ART_APK_TYPE_SPLIT; + } else if (dexInfo instanceof DexUseManagerLocal.CheckedSecondaryDexInfo) { + return ArtStatsLog.ART_DEX2_OAT_REPORTED__APK_TYPE__ART_APK_TYPE_SECONDARY; + } + return ArtStatsLog.ART_DEX2_OAT_REPORTED__APK_TYPE__ART_APK_TYPE_UNKNOWN; + } + + public record Dex2OatResult(int status, int exitCode, int signal) { + public static Dex2OatResult notRun() { + return new Dex2OatResult( + ArtStatsLog.ART_DEX2_OAT_REPORTED__RESULT_STATUS__EXEC_RESULT_STATUS_NOT_RUN, + -1 /* exitCode */, 0 /* signal */); + } + + public static Dex2OatResult exited(int exitCode) { + return new Dex2OatResult( + ArtStatsLog.ART_DEX2_OAT_REPORTED__RESULT_STATUS__EXEC_RESULT_STATUS_EXITED, + exitCode, 0 /* signal */); + } + + public static Dex2OatResult cancelled() { + return new Dex2OatResult( + ArtStatsLog.ART_DEX2_OAT_REPORTED__RESULT_STATUS__EXEC_RESULT_STATUS_CANCELLED, + -1 /* exitCode */, 0 /* signal */); + } + } +} diff --git a/libartservice/service/java/com/android/server/art/DexMetadataHelper.java b/libartservice/service/java/com/android/server/art/DexMetadataHelper.java index 99d92083d3..237bb1878e 100644 --- a/libartservice/service/java/com/android/server/art/DexMetadataHelper.java +++ b/libartservice/service/java/com/android/server/art/DexMetadataHelper.java @@ -23,6 +23,7 @@ import android.os.Build; import androidx.annotation.RequiresApi; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.art.model.DexMetadata; import com.android.server.art.proto.DexMetadataConfig; import java.io.FileNotFoundException; @@ -44,6 +45,9 @@ import java.util.zip.ZipFile; public class DexMetadataHelper { @NonNull private final Injector mInjector; + private static final String PROFILE_DEX_METADATA = "primary.prof"; + private static final String VDEX_DEX_METADATA = "primary.vdex"; + public DexMetadataHelper() { this(new Injector()); } @@ -56,29 +60,31 @@ public class DexMetadataHelper { @NonNull public DexMetadataInfo getDexMetadataInfo(@Nullable DexMetadataPath dmPath) { if (dmPath == null) { - return getDefaultDexMetadataInfo(); + return getDefaultDexMetadataInfo(DexMetadata.TYPE_NONE); } String realDmPath = getDmPath(dmPath); try (var zipFile = mInjector.openZipFile(realDmPath)) { ZipEntry entry = zipFile.getEntry("config.pb"); if (entry == null) { - return new DexMetadataInfo(dmPath, DexMetadataConfig.getDefaultInstance()); + return new DexMetadataInfo( + dmPath, DexMetadataConfig.getDefaultInstance(), getType(zipFile)); } try (InputStream stream = zipFile.getInputStream(entry)) { - return new DexMetadataInfo(dmPath, DexMetadataConfig.parseFrom(stream)); + return new DexMetadataInfo( + dmPath, DexMetadataConfig.parseFrom(stream), getType(zipFile)); } } catch (IOException e) { if (!(e instanceof FileNotFoundException || e instanceof NoSuchFileException)) { AsLog.e(String.format("Failed to read dm file '%s'", realDmPath), e); } - return getDefaultDexMetadataInfo(); + return getDefaultDexMetadataInfo(DexMetadata.TYPE_ERROR); } } @NonNull - private DexMetadataInfo getDefaultDexMetadataInfo() { - return new DexMetadataInfo(null /* dmPath */, DexMetadataConfig.getDefaultInstance()); + private DexMetadataInfo getDefaultDexMetadataInfo(@DexMetadata.Type int type) { + return new DexMetadataInfo(null /* dmPath */, DexMetadataConfig.getDefaultInstance(), type); } @NonNull @@ -88,14 +94,31 @@ public class DexMetadataHelper { return (pos != -1 ? dexPath.substring(0, pos) : dexPath) + ".dm"; } + private static @DexMetadata.Type int getType(@NonNull ZipFile zipFile) { + var profile = zipFile.getEntry(PROFILE_DEX_METADATA); + var vdex = zipFile.getEntry(VDEX_DEX_METADATA); + + if (profile != null && vdex != null) { + return DexMetadata.TYPE_PROFILE_AND_VDEX; + } else if (profile != null) { + return DexMetadata.TYPE_PROFILE; + } else if (vdex != null) { + return DexMetadata.TYPE_VDEX; + } else { + return DexMetadata.TYPE_NONE; + } + } + /** * @param dmPath Represents the path to the dm file, if it exists. Or null if the file doesn't * exist or an error occurred. * @param config The config deserialized from `config.pb`, if it exists. Or the default instance * if the file doesn't exist or an error occurred. + * @param type An enum value representing whether the dm file contains a profile, a VDEX file, + * none, or both. */ - public record DexMetadataInfo( - @Nullable DexMetadataPath dmPath, @NonNull DexMetadataConfig config) {} + public record DexMetadataInfo(@Nullable DexMetadataPath dmPath, + @NonNull DexMetadataConfig config, @DexMetadata.Type int type) {} /** * Injector pattern for testing purpose. diff --git a/libartservice/service/java/com/android/server/art/DexoptHelper.java b/libartservice/service/java/com/android/server/art/DexoptHelper.java index 2369ad4eba..828a05c656 100644 --- a/libartservice/service/java/com/android/server/art/DexoptHelper.java +++ b/libartservice/service/java/com/android/server/art/DexoptHelper.java @@ -29,7 +29,6 @@ import android.os.Binder; import android.os.Build; import android.os.CancellationSignal; import android.os.RemoteException; -import android.os.WorkSource; import androidx.annotation.RequiresApi; @@ -54,7 +53,6 @@ import java.util.Queue; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.function.Function; @@ -71,8 +69,9 @@ import java.util.stream.Collectors; public class DexoptHelper { @NonNull private final Injector mInjector; - public DexoptHelper(@NonNull Context context, @NonNull Config config) { - this(new Injector(context, config)); + public DexoptHelper( + @NonNull Context context, @NonNull Config config, @NonNull Executor reporterExecutor) { + this(new Injector(context, config, reporterExecutor)); } @VisibleForTesting @@ -324,10 +323,13 @@ public class DexoptHelper { public static class Injector { @NonNull private final Context mContext; @NonNull private final Config mConfig; + @NonNull private final Executor mReporterExecutor; - Injector(@NonNull Context context, @NonNull Config config) { + Injector(@NonNull Context context, @NonNull Config config, + @NonNull Executor reporterExecutor) { mContext = context; mConfig = config; + mReporterExecutor = reporterExecutor; // Call the getters for the dependencies that aren't optional, to ensure correct // initialization order. @@ -338,16 +340,16 @@ public class DexoptHelper { PrimaryDexopter getPrimaryDexopter(@NonNull PackageState pkgState, @NonNull AndroidPackage pkg, @NonNull DexoptParams params, @NonNull CancellationSignal cancellationSignal) { - return new PrimaryDexopter( - mContext, mConfig, pkgState, pkg, params, cancellationSignal); + return new PrimaryDexopter(mContext, mConfig, mReporterExecutor, pkgState, pkg, params, + cancellationSignal); } @NonNull SecondaryDexopter getSecondaryDexopter(@NonNull PackageState pkgState, @NonNull AndroidPackage pkg, @NonNull DexoptParams params, @NonNull CancellationSignal cancellationSignal) { - return new SecondaryDexopter( - mContext, mConfig, pkgState, pkg, params, cancellationSignal); + return new SecondaryDexopter(mContext, mConfig, mReporterExecutor, pkgState, pkg, + params, cancellationSignal); } @NonNull diff --git a/libartservice/service/java/com/android/server/art/Dexopter.java b/libartservice/service/java/com/android/server/art/Dexopter.java index 271dc6144f..b13ec66831 100644 --- a/libartservice/service/java/com/android/server/art/Dexopter.java +++ b/libartservice/service/java/com/android/server/art/Dexopter.java @@ -26,10 +26,8 @@ import static com.android.server.art.model.ArtFlags.DexoptFlags; import static com.android.server.art.model.Config.Callback; import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult; -import android.R; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.role.RoleManager; import android.content.Context; import android.content.pm.ApplicationInfo; import android.os.Build; @@ -39,13 +37,12 @@ import android.os.ServiceSpecificException; import android.os.SystemProperties; import android.os.UserManager; import android.os.storage.StorageManager; -import android.text.TextUtils; -import android.util.Pair; import androidx.annotation.RequiresApi; import com.android.internal.annotations.VisibleForTesting; import com.android.server.LocalManagerRegistry; +import com.android.server.art.Dex2OatStatsReporter.Dex2OatResult; import com.android.server.art.model.ArtFlags; import com.android.server.art.model.Config; import com.android.server.art.model.DetailedDexInfo; @@ -64,6 +61,9 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.concurrent.Executor; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** @hide */ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @@ -193,6 +193,7 @@ public abstract class Dexopter<DexInfoType extends DetailedDexInfo> { long cpuTimeMs = 0; long sizeBytes = 0; long sizeBeforeBytes = 0; + Dex2OatResult dex2OatResult = Dex2OatResult.notRun(); @DexoptResult.DexoptResultExtendedStatusFlags int extendedStatusFlags = 0; try { var target = DexoptTarget.<DexInfoType>builder() @@ -276,6 +277,8 @@ public abstract class Dexopter<DexInfoType extends DetailedDexInfo> { cpuTimeMs = dexoptResult.cpuTimeMs; sizeBytes = dexoptResult.sizeBytes; sizeBeforeBytes = dexoptResult.sizeBeforeBytes; + dex2OatResult = dexoptResult.cancelled ? Dex2OatResult.cancelled() + : Dex2OatResult.exited(0); if (status == DexoptResult.DEXOPT_CANCELLED) { return results; @@ -288,6 +291,16 @@ public abstract class Dexopter<DexInfoType extends DetailedDexInfo> { dexInfo.classLoaderContext()), e); status = DexoptResult.DEXOPT_FAILED; + + // Parse status, exit code and signal from the dex2oat error message + Pattern pattern = Pattern.compile( + "\\[status=(-?\\d+),exit_code=(-?\\d+),signal=(-?\\d+)]"); + Matcher matcher = pattern.matcher(Objects.requireNonNull(e.getMessage())); + if (matcher.matches()) { + dex2OatResult = new Dex2OatResult(Integer.parseInt(matcher.group(1)), + Integer.parseInt(matcher.group(2)), + Integer.parseInt(matcher.group(3))); + } } finally { if (!externalProfileErrors.isEmpty()) { extendedStatusFlags |= DexoptResult.EXTENDED_BAD_EXTERNAL_PROFILE; @@ -306,6 +319,17 @@ public abstract class Dexopter<DexInfoType extends DetailedDexInfo> { // Make sure artd does not leak even if the caller holds // `mCancellationSignal` forever. mCancellationSignal.setOnCancelListener(null); + + // Variables used in lambda needs to be effectively final. + Dex2OatResult finalDex2OatResult = dex2OatResult; + mInjector.getReporterExecutor().execute( + () + -> Dex2OatStatsReporter.report(mPkgState.getAppId(), + result.getActualCompilerFilter(), + mParams.getReason(), dmInfo.type(), dexInfo, + abi.isa(), finalDex2OatResult, + result.getSizeBytes(), + result.getDex2oatWallTimeMillis())); } } @@ -757,10 +781,13 @@ public abstract class Dexopter<DexInfoType extends DetailedDexInfo> { public static class Injector { @NonNull private final Context mContext; @NonNull private final Config mConfig; + @NonNull private final Executor mReporterExecutor; - public Injector(@NonNull Context context, @NonNull Config config) { + public Injector(@NonNull Context context, @NonNull Config config, + @NonNull Executor reporterExecutor) { mContext = context; mConfig = config; + mReporterExecutor = reporterExecutor; // Call the getters for various dependencies, to ensure correct initialization order. getUserManager(); @@ -823,6 +850,11 @@ public abstract class Dexopter<DexInfoType extends DetailedDexInfo> { } @NonNull + public Executor getReporterExecutor() { + return mReporterExecutor; + } + + @NonNull public DexMetadataHelper getDexMetadataHelper() { return new DexMetadataHelper(); } diff --git a/libartservice/service/java/com/android/server/art/PrimaryDexopter.java b/libartservice/service/java/com/android/server/art/PrimaryDexopter.java index f8234c4df9..eb200592aa 100644 --- a/libartservice/service/java/com/android/server/art/PrimaryDexopter.java +++ b/libartservice/service/java/com/android/server/art/PrimaryDexopter.java @@ -30,7 +30,6 @@ import android.os.Process; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.os.UserHandle; -import android.text.TextUtils; import androidx.annotation.RequiresApi; @@ -39,18 +38,12 @@ import com.android.modules.utils.pm.PackageStateModulesUtils; import com.android.server.art.model.ArtFlags; import com.android.server.art.model.Config; import com.android.server.art.model.DexoptParams; -import com.android.server.art.model.DexoptResult; -import com.android.server.pm.PackageManagerLocal; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageState; -import dalvik.system.DexFile; - -import com.google.auto.value.AutoValue; - -import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.concurrent.Executor; /** @hide */ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @@ -58,9 +51,10 @@ public class PrimaryDexopter extends Dexopter<DetailedPrimaryDexInfo> { private final int mSharedGid; public PrimaryDexopter(@NonNull Context context, @NonNull Config config, - @NonNull PackageState pkgState, @NonNull AndroidPackage pkg, + Executor reporterExecutor, @NonNull PackageState pkgState, @NonNull AndroidPackage pkg, @NonNull DexoptParams params, @NonNull CancellationSignal cancellationSignal) { - this(new Injector(context, config), pkgState, pkg, params, cancellationSignal); + this(new Injector(context, config, reporterExecutor), pkgState, pkg, params, + cancellationSignal); } @VisibleForTesting diff --git a/libartservice/service/java/com/android/server/art/SecondaryDexopter.java b/libartservice/service/java/com/android/server/art/SecondaryDexopter.java index 57fe174d6a..b8ee9d079d 100644 --- a/libartservice/service/java/com/android/server/art/SecondaryDexopter.java +++ b/libartservice/service/java/com/android/server/art/SecondaryDexopter.java @@ -36,14 +36,16 @@ import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageState; import java.util.List; +import java.util.concurrent.Executor; /** @hide */ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public class SecondaryDexopter extends Dexopter<CheckedSecondaryDexInfo> { public SecondaryDexopter(@NonNull Context context, @NonNull Config config, - @NonNull PackageState pkgState, @NonNull AndroidPackage pkg, + Executor reporterExecutor, @NonNull PackageState pkgState, @NonNull AndroidPackage pkg, @NonNull DexoptParams params, @NonNull CancellationSignal cancellationSignal) { - this(new Injector(context, config), pkgState, pkg, params, cancellationSignal); + this(new Injector(context, config, reporterExecutor), pkgState, pkg, params, + cancellationSignal); } @VisibleForTesting diff --git a/libartservice/service/java/com/android/server/art/model/DexMetadata.java b/libartservice/service/java/com/android/server/art/model/DexMetadata.java new file mode 100644 index 0000000000..5895b7be30 --- /dev/null +++ b/libartservice/service/java/com/android/server/art/model/DexMetadata.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2024 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 java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** @hide */ +public class DexMetadata { + /** An explicit private class to avoid exposing constructor.*/ + private DexMetadata() {} + + public static final int TYPE_UNKNOWN = 0; + public static final int TYPE_PROFILE = 1; + public static final int TYPE_VDEX = 2; + public static final int TYPE_PROFILE_AND_VDEX = 3; + public static final int TYPE_NONE = 4; + public static final int TYPE_ERROR = 5; + + /** @hide */ + // clang-format off + @IntDef(prefix = "TYPE_", value = { + TYPE_UNKNOWN, + TYPE_PROFILE, + TYPE_VDEX, + TYPE_PROFILE_AND_VDEX, + TYPE_NONE, + TYPE_ERROR, + }) + // clang-format on + @Retention(RetentionPolicy.SOURCE) + public @interface Type {} +} diff --git a/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java index 739c2daaa9..9deb3f1055 100644 --- a/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java +++ b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java @@ -35,7 +35,6 @@ import static org.mockito.ArgumentMatchers.matches; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.anyLong; import static org.mockito.Mockito.argThat; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.isNull; diff --git a/libartservice/service/javatests/com/android/server/art/DexoptHelperTest.java b/libartservice/service/javatests/com/android/server/art/DexoptHelperTest.java index ac5947930b..f6ed721157 100644 --- a/libartservice/service/javatests/com/android/server/art/DexoptHelperTest.java +++ b/libartservice/service/javatests/com/android/server/art/DexoptHelperTest.java @@ -25,7 +25,6 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; import static org.mockito.Mockito.any; -import static org.mockito.Mockito.anyLong; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.lenient; @@ -34,7 +33,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.same; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.apphibernation.AppHibernationManager; diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexopterParameterizedTest.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterParameterizedTest.java index 49d7c6cb41..10f9f59e91 100644 --- a/libartservice/service/javatests/com/android/server/art/PrimaryDexopterParameterizedTest.java +++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterParameterizedTest.java @@ -51,8 +51,6 @@ import com.android.server.art.model.DexoptParams; import com.android.server.art.model.DexoptResult; import com.android.server.art.testing.TestingUtils; -import dalvik.system.DexFile; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -344,7 +342,7 @@ public class PrimaryDexopterParameterizedTest extends PrimaryDexopterTestBase { .when(mArtd) .getDexoptNeeded("/somewhere/app/foo/base.apk", "arm", "PCL[]", mParams.mExpectedCompilerFilter, mParams.mExpectedDexoptTrigger); - doThrow(ServiceSpecificException.class) + doThrow(new ServiceSpecificException(31, "This is an error message.")) .when(mArtd) .dexopt(deepEq(buildOutputArtifacts("/somewhere/app/foo/base.apk", "arm", mParams.mIsInDalvikCache, permissionSettings, diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTest.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTest.java index 0030085497..5cc201cd37 100644 --- a/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTest.java +++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTest.java @@ -577,7 +577,7 @@ public class PrimaryDexopterTest extends PrimaryDexopterTestBase { when(mArtd.dexopt(any(), eq(mDexPath), any(), any(), any(), any(), any(), any(), anyInt(), any(), any())) - .thenThrow(ServiceSpecificException.class); + .thenThrow(new ServiceSpecificException(42, "This is an error message")); mPrimaryDexopter.dexopt(); diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTestBase.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTestBase.java index 3105e4e51b..d94190ded5 100644 --- a/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTestBase.java +++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTestBase.java @@ -46,6 +46,7 @@ import org.mockito.Mock; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.ThreadPoolExecutor; public class PrimaryDexopterTestBase { protected static final String PKG_NAME = "com.example.foo"; @@ -65,6 +66,7 @@ public class PrimaryDexopterTestBase { @Mock protected DexUseManagerLocal mDexUseManager; @Mock protected StorageManager mStorageManager; @Mock protected DexMetadataHelper.Injector mDexMetadataHelperInjector; + @Mock protected ThreadPoolExecutor mReporterExecutor; protected PackageState mPkgState; protected AndroidPackage mPkg; protected PackageUserState mPkgUserStateNotInstalled; @@ -91,6 +93,7 @@ public class PrimaryDexopterTestBase { lenient().when(mInjector.getStorageManager()).thenReturn(mStorageManager); lenient().when(mInjector.getArtVersion()).thenReturn(ART_VERSION); lenient().when(mInjector.getConfig()).thenReturn(mConfig); + lenient().when(mInjector.getReporterExecutor()).thenReturn(mReporterExecutor); lenient().when(mInjector.getDexMetadataHelper()).thenReturn(mDexMetadataHelper); lenient().when(mInjector.isPreReboot()).thenReturn(false); diff --git a/libartservice/service/javatests/com/android/server/art/SecondaryDexopterTest.java b/libartservice/service/javatests/com/android/server/art/SecondaryDexopterTest.java index 07dd07dae7..074edc0629 100644 --- a/libartservice/service/javatests/com/android/server/art/SecondaryDexopterTest.java +++ b/libartservice/service/javatests/com/android/server/art/SecondaryDexopterTest.java @@ -61,6 +61,7 @@ import org.mockito.Mock; import java.util.List; import java.util.Set; +import java.util.concurrent.ThreadPoolExecutor; import java.util.function.Function; @SmallTest @@ -108,6 +109,7 @@ public class SecondaryDexopterTest { @Mock private IArtd mArtd; @Mock private DexUseManagerLocal mDexUseManager; @Mock private DexMetadataHelper.Injector mDexMetadataHelperInjector; + @Mock private ThreadPoolExecutor mReporterExecutor; private PackageState mPkgState; private AndroidPackage mPkg; private CancellationSignal mCancellationSignal; @@ -144,6 +146,7 @@ public class SecondaryDexopterTest { lenient().when(mInjector.isLauncherPackage(any())).thenReturn(false); lenient().when(mInjector.getDexUseManager()).thenReturn(mDexUseManager); lenient().when(mInjector.getConfig()).thenReturn(mConfig); + lenient().when(mInjector.getReporterExecutor()).thenReturn(mReporterExecutor); lenient().when(mInjector.getDexMetadataHelper()).thenReturn(mDexMetadataHelper); List<CheckedSecondaryDexInfo> secondaryDexInfo = createSecondaryDexInfo(); diff --git a/odrefresh/odr_metrics_record.h b/odrefresh/odr_metrics_record.h index 4b18edb6d6..47db41978a 100644 --- a/odrefresh/odr_metrics_record.h +++ b/odrefresh/odr_metrics_record.h @@ -34,7 +34,8 @@ constexpr const char* kOdrefreshMetricsFile = "/data/misc/odrefresh/odrefresh-me static constexpr int32_t kOdrefreshMetricsVersion = 4; // Constant value used in ExecResult when the process was not run at all. -// Mirrors EXEC_RESULT_STATUS_NOT_RUN contained in frameworks/proto_logging/atoms.proto. +// Mirrors EXEC_RESULT_STATUS_NOT_RUN contained in +// frameworks/proto_logging/stats/enums/art/common_enums.proto. static constexpr int32_t kExecResultNotRun = 5; static_assert(kExecResultNotRun > ExecResult::Status::kLast, "`art::odrefresh::kExecResultNotRun` value should not overlap with" diff --git a/runtime/exec_utils.h b/runtime/exec_utils.h index 61e9beae32..6a97cc7e5b 100644 --- a/runtime/exec_utils.h +++ b/runtime/exec_utils.h @@ -48,7 +48,7 @@ struct ExecCallbacks { struct ExecResult { // This struct needs to be in sync with the ExecResultStatus enum contained within the - // OdrefreshReported atom in frameworks/proto_logging/atoms/art/odrefresh_extension_atoms.proto. + // OdrefreshReported atom in frameworks/proto_logging/stats/enums/art/common_enums.proto. enum Status { // Unable to get the status. kUnknown = 0, |