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