1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.art.model; 18 19 import android.annotation.DurationMillisLong; 20 import android.annotation.IntDef; 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.annotation.SystemApi; 24 25 import com.android.internal.annotations.Immutable; 26 import com.android.internal.annotations.VisibleForTesting; 27 28 import com.google.auto.value.AutoValue; 29 30 import java.lang.annotation.Retention; 31 import java.lang.annotation.RetentionPolicy; 32 import java.util.ArrayList; 33 import java.util.Collections; 34 import java.util.List; 35 36 /** @hide */ 37 @SystemApi(client = SystemApi.Client.SYSTEM_SERVER) 38 @Immutable 39 @AutoValue 40 public abstract class DexoptResult { 41 // Possible values of {@link #DexoptResultStatus}. 42 // A larger number means a higher priority. If multiple dex container files are processed, the 43 // final status will be the one with the highest priority. 44 /** Dexopt is skipped because there is no need to do it. */ 45 public static final int DEXOPT_SKIPPED = 10; 46 /** Dexopt is performed successfully. */ 47 public static final int DEXOPT_PERFORMED = 20; 48 /** Dexopt is failed. */ 49 public static final int DEXOPT_FAILED = 30; 50 /** Dexopt is cancelled. */ 51 public static final int DEXOPT_CANCELLED = 40; 52 53 /** @hide */ 54 // clang-format off 55 @IntDef(prefix = {"DEXOPT_"}, value = { 56 DEXOPT_SKIPPED, 57 DEXOPT_FAILED, 58 DEXOPT_PERFORMED, 59 DEXOPT_CANCELLED, 60 }) 61 // clang-format on 62 @Retention(RetentionPolicy.SOURCE) 63 public @interface DexoptResultStatus {} 64 65 // Possible values of {@link #DexoptResultExtendedStatusFlags}. 66 /** Dexopt is skipped because the remaining storage space is low. */ 67 public static final int EXTENDED_SKIPPED_STORAGE_LOW = 1 << 0; 68 /** 69 * Dexopt is skipped because the dex container file has no dex code while the manifest declares 70 * that it does. 71 * 72 * Note that this flag doesn't apply to dex container files that are not declared to have code. 73 * Instead, those files are not listed in {@link 74 * PackageDexoptResult#getDexContainerFileDexoptResults} in the first place. 75 */ 76 public static final int EXTENDED_SKIPPED_NO_DEX_CODE = 1 << 1; 77 /** 78 * Dexopt is skipped because this operation is for Pre-reboot and the Pre-reboot artifacts 79 * already exist. 80 * 81 * @hide 82 */ 83 public static final int EXTENDED_SKIPPED_PRE_REBOOT_ALREADY_EXIST = 1 << 3; 84 /** 85 * Dexopt encountered errors when processing the profiles that are external to the device, 86 * including the profile in the DM file and the profile embedded in the dex container file. 87 * Details of the errors can be found in {@link 88 * DexContainerFileDexoptResult#getExternalProfileErrors}. 89 * 90 * This is not a critical error. Dexopt may still have succeeded after ignoring the bad external 91 * profiles. 92 */ 93 public static final int EXTENDED_BAD_EXTERNAL_PROFILE = 1 << 2; 94 95 /** @hide */ 96 // clang-format off 97 @IntDef(flag = true, prefix = {"EXTENDED_"}, value = { 98 EXTENDED_SKIPPED_STORAGE_LOW, 99 EXTENDED_SKIPPED_NO_DEX_CODE, 100 EXTENDED_SKIPPED_PRE_REBOOT_ALREADY_EXIST, 101 EXTENDED_BAD_EXTERNAL_PROFILE, 102 }) 103 // clang-format on 104 @Retention(RetentionPolicy.SOURCE) 105 public @interface DexoptResultExtendedStatusFlags {} 106 107 /** @hide */ DexoptResult()108 protected DexoptResult() {} 109 110 /** @hide */ create(@onNull String requestedCompilerFilter, @NonNull String reason, @NonNull List<PackageDexoptResult> packageDexoptResult)111 public static @NonNull DexoptResult create(@NonNull String requestedCompilerFilter, 112 @NonNull String reason, @NonNull List<PackageDexoptResult> packageDexoptResult) { 113 return new AutoValue_DexoptResult(requestedCompilerFilter, reason, packageDexoptResult); 114 } 115 116 /** @hide */ 117 @VisibleForTesting create()118 public static @NonNull DexoptResult create() { 119 return new AutoValue_DexoptResult( 120 "compiler-filter", "reason", List.of() /* packageDexoptResult */); 121 } 122 123 /** 124 * The requested compiler filter. Note that the compiler filter might be adjusted before the 125 * execution based on factors like dexopt flags, whether the profile is available, or whether 126 * the app is used by other apps. 127 * 128 * @see DexoptParams.Builder#setCompilerFilter(String) 129 * @see DexContainerFileDexoptResult#getActualCompilerFilter() 130 */ getRequestedCompilerFilter()131 public abstract @NonNull String getRequestedCompilerFilter(); 132 133 /** The compilation reason. */ getReason()134 public abstract @NonNull String getReason(); 135 136 /** 137 * The result of each individual package. 138 * 139 * If the request is to dexopt a single package without dexopting dependencies, the only 140 * element is the result of the requested package. 141 * 142 * If the request is to dexopt a single package with {@link 143 * ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES} set, the first element is the result of the 144 * requested package, and the rest are the results of the dependency packages. 145 * 146 * If the request is to dexopt multiple packages, the list contains the results of all the 147 * requested packages. The results of their dependency packages are also included if {@link 148 * ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES} is set. 149 * 150 * If the request is a batch dexopt operation that got cancelled, the list still has an entry 151 * for every package that was requested to be optimized. 152 */ getPackageDexoptResults()153 public abstract @NonNull List<PackageDexoptResult> getPackageDexoptResults(); 154 155 /** The final status. */ getFinalStatus()156 public @DexoptResultStatus int getFinalStatus() { 157 return getPackageDexoptResults() 158 .stream() 159 .mapToInt(result -> result.getStatus()) 160 .max() 161 .orElse(DEXOPT_SKIPPED); 162 } 163 164 /** @hide */ 165 @NonNull dexoptResultStatusToString(@exoptResultStatus int status)166 public static String dexoptResultStatusToString(@DexoptResultStatus int status) { 167 switch (status) { 168 case DexoptResult.DEXOPT_SKIPPED: 169 return "SKIPPED"; 170 case DexoptResult.DEXOPT_PERFORMED: 171 return "PERFORMED"; 172 case DexoptResult.DEXOPT_FAILED: 173 return "FAILED"; 174 case DexoptResult.DEXOPT_CANCELLED: 175 return "CANCELLED"; 176 } 177 throw new IllegalArgumentException("Unknown dexopt status " + status); 178 } 179 180 /** @hide */ 181 @NonNull dexoptResultExtendedStatusFlagsToString( @exoptResultExtendedStatusFlags int flags)182 public static String dexoptResultExtendedStatusFlagsToString( 183 @DexoptResultExtendedStatusFlags int flags) { 184 var strs = new ArrayList<String>(); 185 if ((flags & DexoptResult.EXTENDED_SKIPPED_STORAGE_LOW) != 0) { 186 strs.add("EXTENDED_SKIPPED_STORAGE_LOW"); 187 } 188 if ((flags & DexoptResult.EXTENDED_SKIPPED_NO_DEX_CODE) != 0) { 189 strs.add("EXTENDED_SKIPPED_NO_DEX_CODE"); 190 } 191 if ((flags & DexoptResult.EXTENDED_SKIPPED_PRE_REBOOT_ALREADY_EXIST) != 0) { 192 strs.add("EXTENDED_SKIPPED_PRE_REBOOT_ALREADY_EXIST"); 193 } 194 if ((flags & DexoptResult.EXTENDED_BAD_EXTERNAL_PROFILE) != 0) { 195 strs.add("EXTENDED_BAD_EXTERNAL_PROFILE"); 196 } 197 return String.join(", ", strs); 198 } 199 200 /** 201 * Describes the result of a package. 202 * 203 * @hide 204 */ 205 @SystemApi(client = SystemApi.Client.SYSTEM_SERVER) 206 @Immutable 207 @AutoValue 208 public static abstract class PackageDexoptResult { 209 /** @hide */ PackageDexoptResult()210 protected PackageDexoptResult() {} 211 212 /** @hide */ create(@onNull String packageName, @NonNull List<DexContainerFileDexoptResult> dexContainerFileDexoptResults, @Nullable @DexoptResultStatus Integer packageLevelStatus)213 public static @NonNull PackageDexoptResult create(@NonNull String packageName, 214 @NonNull List<DexContainerFileDexoptResult> dexContainerFileDexoptResults, 215 @Nullable @DexoptResultStatus Integer packageLevelStatus) { 216 return new AutoValue_DexoptResult_PackageDexoptResult( 217 packageName, dexContainerFileDexoptResults, packageLevelStatus); 218 } 219 220 /** The package name. */ getPackageName()221 public abstract @NonNull String getPackageName(); 222 223 /** 224 * The results of dexopting dex container files. Note that there can be multiple entries 225 * for the same dex container file, but for different ABIs. 226 */ 227 public abstract @NonNull List<DexContainerFileDexoptResult> getDexContainerFileDexoptResults()228 getDexContainerFileDexoptResults(); 229 230 /** @hide */ getPackageLevelStatus()231 @Nullable @DexoptResultStatus public abstract Integer getPackageLevelStatus(); 232 233 /** The overall status of the package. */ getStatus()234 public @DexoptResultStatus int getStatus() { 235 return getPackageLevelStatus() != null ? getPackageLevelStatus() 236 : getDexContainerFileDexoptResults() 237 .stream() 238 .mapToInt(result -> result.getStatus()) 239 .max() 240 .orElse(DEXOPT_SKIPPED); 241 } 242 243 /** True if the package has any artifacts updated by this operation. */ hasUpdatedArtifacts()244 public boolean hasUpdatedArtifacts() { 245 return getDexContainerFileDexoptResults().stream().anyMatch( 246 result -> result.getStatus() == DEXOPT_PERFORMED); 247 } 248 } 249 250 /** 251 * Describes the result of dexopting a dex container file. 252 * 253 * @hide 254 */ 255 @SystemApi(client = SystemApi.Client.SYSTEM_SERVER) 256 @Immutable 257 @AutoValue 258 @SuppressWarnings("AutoValueImmutableFields") // Can't use ImmutableList because it's in Guava. 259 public static abstract class DexContainerFileDexoptResult { 260 /** @hide */ DexContainerFileDexoptResult()261 protected DexContainerFileDexoptResult() {} 262 263 /** @hide */ create(@onNull String dexContainerFile, boolean isPrimaryAbi, @NonNull String abi, @NonNull String compilerFilter, @DexoptResultStatus int status, long dex2oatWallTimeMillis, long dex2oatCpuTimeMillis, long sizeBytes, long sizeBeforeBytes, @DexoptResultExtendedStatusFlags int extendedStatusFlags, @NonNull List<String> externalProfileErrors)264 public static @NonNull DexContainerFileDexoptResult create(@NonNull String dexContainerFile, 265 boolean isPrimaryAbi, @NonNull String abi, @NonNull String compilerFilter, 266 @DexoptResultStatus int status, long dex2oatWallTimeMillis, 267 long dex2oatCpuTimeMillis, long sizeBytes, long sizeBeforeBytes, 268 @DexoptResultExtendedStatusFlags int extendedStatusFlags, 269 @NonNull List<String> externalProfileErrors) { 270 return new AutoValue_DexoptResult_DexContainerFileDexoptResult(dexContainerFile, 271 isPrimaryAbi, abi, compilerFilter, status, dex2oatWallTimeMillis, 272 dex2oatCpuTimeMillis, sizeBytes, sizeBeforeBytes, extendedStatusFlags, 273 Collections.unmodifiableList(externalProfileErrors)); 274 } 275 276 /** @hide */ 277 @VisibleForTesting create(@onNull String dexContainerFile, boolean isPrimaryAbi, @NonNull String abi, @NonNull String compilerFilter, @DexoptResultStatus int status)278 public static @NonNull DexContainerFileDexoptResult create(@NonNull String dexContainerFile, 279 boolean isPrimaryAbi, @NonNull String abi, @NonNull String compilerFilter, 280 @DexoptResultStatus int status) { 281 return create(dexContainerFile, isPrimaryAbi, abi, compilerFilter, status, 282 0 /* dex2oatWallTimeMillis */, 0 /* dex2oatCpuTimeMillis */, 0 /* sizeBytes */, 283 0 /* sizeBeforeBytes */, 0 /* extendedStatusFlags */, 284 List.of() /* externalProfileErrors */); 285 } 286 287 /** The absolute path to the dex container file. */ getDexContainerFile()288 public abstract @NonNull String getDexContainerFile(); 289 290 /** 291 * If true, the dexopt is for the primary ABI of the package (the ABI that the 292 * application is launched with). Otherwise, the dexopt is for an ABI that other 293 * applications might be launched with when using this application's code. 294 */ isPrimaryAbi()295 public abstract boolean isPrimaryAbi(); 296 297 /** 298 * Returns the ABI that the dexopt is for. Possible values are documented at 299 * https://developer.android.com/ndk/guides/abis#sa. 300 */ getAbi()301 public abstract @NonNull String getAbi(); 302 303 /** 304 * The actual compiler filter. 305 * 306 * @see DexoptParams.Builder#setCompilerFilter(String) 307 */ getActualCompilerFilter()308 public abstract @NonNull String getActualCompilerFilter(); 309 310 /** The status of dexopting this dex container file. */ getStatus()311 public abstract @DexoptResultStatus int getStatus(); 312 313 /** 314 * The wall time of the dex2oat invocation, in milliseconds, if dex2oat succeeded or was 315 * cancelled. Returns 0 if dex2oat failed or was not run, or if failed to get the value. 316 */ getDex2oatWallTimeMillis()317 public abstract @DurationMillisLong long getDex2oatWallTimeMillis(); 318 319 /** 320 * The CPU time of the dex2oat invocation, in milliseconds, if dex2oat succeeded or was 321 * cancelled. Returns 0 if dex2oat failed or was not run, or if failed to get the value. 322 */ getDex2oatCpuTimeMillis()323 public abstract @DurationMillisLong long getDex2oatCpuTimeMillis(); 324 325 /** 326 * The total size, in bytes, of the dexopt artifacts. Returns 0 if {@link #getStatus()} 327 * is not {@link #DEXOPT_PERFORMED}. 328 */ getSizeBytes()329 public abstract long getSizeBytes(); 330 331 /** 332 * The total size, in bytes, of the previous dexopt artifacts that has been replaced. 333 * Returns 0 if there were no previous dexopt artifacts or {@link #getStatus()} is not 334 * {@link #DEXOPT_PERFORMED}. 335 */ getSizeBeforeBytes()336 public abstract long getSizeBeforeBytes(); 337 338 /** 339 * A bitfield of the extended status flags. 340 * 341 * Flags that starts with `EXTENDED_SKIPPED_` are a subset of the reasons why dexopt is 342 * skipped. Note that they don't cover all possible reasons. At most one `EXTENDED_SKIPPED_` 343 * flag will be set, even if the situation meets multiple `EXTENDED_SKIPPED_` flags. The 344 * order of precedence of those flags is undefined. 345 */ getExtendedStatusFlags()346 public abstract @DexoptResultExtendedStatusFlags int getExtendedStatusFlags(); 347 348 /** 349 * Details of errors occurred when processing external profiles, one error per profile file 350 * that the dexopter tried to read. 351 * 352 * If the same dex container file is dexopted for multiple ABIs, the same profile errors 353 * will be repeated for each ABI in the {@link DexContainerFileDexoptResult}s of the same 354 * dex container file. 355 * 356 * The error messages are for logging only, and they include the paths to the profile files 357 * that caused the errors. 358 * 359 * @see #EXTENDED_BAD_EXTERNAL_PROFILE. 360 */ getExternalProfileErrors()361 public abstract @NonNull List<String> getExternalProfileErrors(); 362 363 @Override 364 @NonNull toString()365 public String toString() { 366 return String.format("DexContainerFileDexoptResult{" 367 + "dexContainerFile=%s, " 368 + "primaryAbi=%b, " 369 + "abi=%s, " 370 + "actualCompilerFilter=%s, " 371 + "status=%s, " 372 + "dex2oatWallTimeMillis=%d, " 373 + "dex2oatCpuTimeMillis=%d, " 374 + "sizeBytes=%d, " 375 + "sizeBeforeBytes=%d, " 376 + "extendedStatusFlags=[%s]}", 377 getDexContainerFile(), isPrimaryAbi(), getAbi(), getActualCompilerFilter(), 378 DexoptResult.dexoptResultStatusToString(getStatus()), 379 getDex2oatWallTimeMillis(), getDex2oatCpuTimeMillis(), getSizeBytes(), 380 getSizeBeforeBytes(), 381 DexoptResult.dexoptResultExtendedStatusFlagsToString(getExtendedStatusFlags())); 382 } 383 } 384 } 385