1 /*
2  * Copyright (C) 2021 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.car.watchdog;
18 
19 import static com.android.car.watchdog.WatchdogPerfHandler.INTERNAL_APPLICATION_CATEGORY_TYPE_MAPS;
20 import static com.android.car.watchdog.WatchdogPerfHandler.INTERNAL_APPLICATION_CATEGORY_TYPE_MEDIA;
21 
22 import android.automotive.watchdog.PerStateBytes;
23 import android.automotive.watchdog.internal.ApplicationCategoryType;
24 import android.automotive.watchdog.internal.ComponentType;
25 import android.automotive.watchdog.internal.IoOveruseConfiguration;
26 import android.automotive.watchdog.internal.PackageMetadata;
27 import android.automotive.watchdog.internal.PerStateIoOveruseThreshold;
28 import android.automotive.watchdog.internal.ResourceOveruseConfiguration;
29 import android.automotive.watchdog.internal.ResourceSpecificConfiguration;
30 import android.util.ArrayMap;
31 import android.util.ArraySet;
32 import android.util.IndentingPrintWriter;
33 import android.util.SparseArray;
34 
35 import com.android.internal.annotations.GuardedBy;
36 
37 import java.util.ArrayList;
38 import java.util.List;
39 import java.util.Set;
40 import java.util.function.BiFunction;
41 
42 /**
43  * Cache to store overuse configurations in memory.
44  *
45  * <p>It assumes that the error checking and loading/merging initial configs are done prior to
46  * setting the cache.
47  */
48 public final class OveruseConfigurationCache {
49     static final PerStateBytes DEFAULT_THRESHOLD =
50             constructPerStateBytes(Long.MAX_VALUE, Long.MAX_VALUE, Long.MAX_VALUE);
51 
52     private final Object mLock = new Object();
53     @GuardedBy("mLock")
54     private final ArraySet<String> mSafeToKillSystemPackages = new ArraySet<>();
55     @GuardedBy("mLock")
56     private final ArraySet<String> mSafeToKillVendorPackages = new ArraySet<>();
57     @GuardedBy("mLock")
58     private final List<String> mVendorPackagePrefixes = new ArrayList<>();
59     @GuardedBy("mLock")
60     private final SparseArray<ArraySet<String>> mPackagesByAppCategoryType = new SparseArray<>();
61     @GuardedBy("mLock")
62     private final SparseArray<PerStateBytes> mGenericIoThresholdsByComponent = new SparseArray<>();
63     @GuardedBy("mLock")
64     private final ArrayMap<String, PerStateBytes> mIoThresholdsBySystemPackages = new ArrayMap<>();
65     @GuardedBy("mLock")
66     private final ArrayMap<String, PerStateBytes> mIoThresholdsByVendorPackages = new ArrayMap<>();
67     @GuardedBy("mLock")
68     private final SparseArray<PerStateBytes> mIoThresholdsByAppCategoryType = new SparseArray<>();
69 
70     /** Dumps the contents of the cache. */
dump(IndentingPrintWriter writer)71     public void dump(IndentingPrintWriter writer) {
72         writer.println("*" + getClass().getSimpleName() + "*");
73         writer.increaseIndent();
74         synchronized (mLock) {
75             writer.println("mSafeToKillSystemPackages: " + mSafeToKillSystemPackages);
76             writer.println("mSafeToKillVendorPackages: " + mSafeToKillVendorPackages);
77             writer.println("mVendorPackagePrefixes: " + mVendorPackagePrefixes);
78             writer.println("mPackagesByAppCategoryType: ");
79             writer.increaseIndent();
80             for (int i = 0; i < mPackagesByAppCategoryType.size(); ++i) {
81                 writer.print("App category: "
82                         + toApplicationCategoryTypeString(mPackagesByAppCategoryType.keyAt(i)));
83                 writer.println(", Packages: " + mPackagesByAppCategoryType.valueAt(i));
84             }
85             writer.decreaseIndent();
86             writer.println("mGenericIoThresholdsByComponent: ");
87             writer.increaseIndent();
88             for (int i = 0; i < mGenericIoThresholdsByComponent.size(); ++i) {
89                 writer.print("Component type: "
90                         + toComponentTypeString(mGenericIoThresholdsByComponent.keyAt(i)));
91                 writer.print(", Threshold: ");
92                 dumpPerStateBytes(mGenericIoThresholdsByComponent.valueAt(i), writer);
93             }
94             writer.decreaseIndent();
95             writer.println("mIoThresholdsBySystemPackages: ");
96             writer.increaseIndent();
97             for (int i = 0; i < mIoThresholdsBySystemPackages.size(); ++i) {
98                 writer.print("Package name: " + mIoThresholdsBySystemPackages.keyAt(i));
99                 writer.print(", Threshold: ");
100                 dumpPerStateBytes(mIoThresholdsBySystemPackages.valueAt(i), writer);
101             }
102             writer.decreaseIndent();
103             writer.println("mIoThresholdsByVendorPackages: ");
104             writer.increaseIndent();
105             for (int i = 0; i < mIoThresholdsByVendorPackages.size(); ++i) {
106                 writer.print("Package name: " + mIoThresholdsByVendorPackages.keyAt(i));
107                 writer.print(", Threshold: ");
108                 dumpPerStateBytes(mIoThresholdsByVendorPackages.valueAt(i), writer);
109             }
110             writer.decreaseIndent();
111             writer.println("mIoThresholdsByAppCategoryType: ");
112             writer.increaseIndent();
113             for (int i = 0; i < mIoThresholdsByAppCategoryType.size(); ++i) {
114                 writer.print("App category: "
115                         + toApplicationCategoryTypeString(mIoThresholdsByAppCategoryType.keyAt(i)));
116                 writer.print(", Threshold: ");
117                 dumpPerStateBytes(mIoThresholdsByAppCategoryType.valueAt(i), writer);
118             }
119             writer.decreaseIndent();
120         }
121         writer.decreaseIndent();
122     }
123 
124     /** Overwrites the configurations in the cache. */
set(List<ResourceOveruseConfiguration> configs)125     public void set(List<ResourceOveruseConfiguration> configs) {
126         synchronized (mLock) {
127             clearLocked();
128             for (int i = 0; i < configs.size(); i++) {
129                 ResourceOveruseConfiguration config = configs.get(i);
130                 switch (config.componentType) {
131                     case ComponentType.SYSTEM:
132                         mSafeToKillSystemPackages.addAll(config.safeToKillPackages);
133                         break;
134                     case ComponentType.VENDOR:
135                         mSafeToKillVendorPackages.addAll(config.safeToKillPackages);
136                         mVendorPackagePrefixes.addAll(config.vendorPackagePrefixes);
137                         for (int j = 0; j < config.packageMetadata.size(); ++j) {
138                             PackageMetadata meta = config.packageMetadata.get(j);
139                             ArraySet<String> packages =
140                                     mPackagesByAppCategoryType.get(meta.appCategoryType);
141                             if (packages == null) {
142                                 packages = new ArraySet<>();
143                             }
144                             packages.add(meta.packageName);
145                             mPackagesByAppCategoryType.append(meta.appCategoryType, packages);
146                         }
147                         break;
148                     default:
149                         // All third-party apps are killable.
150                         break;
151                 }
152                 for (int j = 0; j < config.resourceSpecificConfigurations.size(); ++j) {
153                     if (config.resourceSpecificConfigurations.get(j).getTag()
154                             == ResourceSpecificConfiguration.ioOveruseConfiguration) {
155                         setIoThresholdsLocked(config.componentType,
156                                 config.resourceSpecificConfigurations.get(j)
157                                         .getIoOveruseConfiguration());
158                     }
159                 }
160             }
161         }
162     }
163 
164     /** Returns the threshold for the given package and component type. */
fetchThreshold(String genericPackageName, @ComponentType int componentType)165     public PerStateBytes fetchThreshold(String genericPackageName,
166             @ComponentType int componentType) {
167         synchronized (mLock) {
168             PerStateBytes threshold = null;
169             switch (componentType) {
170                 case ComponentType.SYSTEM:
171                     threshold = mIoThresholdsBySystemPackages.get(genericPackageName);
172                     if (threshold != null) {
173                         return copyPerStateBytes(threshold);
174                     }
175                     break;
176                 case ComponentType.VENDOR:
177                     threshold = mIoThresholdsByVendorPackages.get(genericPackageName);
178                     if (threshold != null) {
179                         return copyPerStateBytes(threshold);
180                     }
181                     break;
182             }
183             threshold = fetchAppCategorySpecificThresholdLocked(genericPackageName);
184             if (threshold != null) {
185                 return copyPerStateBytes(threshold);
186             }
187             threshold = mGenericIoThresholdsByComponent.get(componentType);
188             return threshold != null ? copyPerStateBytes(threshold)
189                     : copyPerStateBytes(DEFAULT_THRESHOLD);
190         }
191     }
192 
193     /** Returns whether or not the given package is safe-to-kill on resource overuse. */
isSafeToKill(String genericPackageName, @ComponentType int componentType, List<String> sharedPackages)194     public boolean isSafeToKill(String genericPackageName, @ComponentType int componentType,
195             List<String> sharedPackages) {
196         synchronized (mLock) {
197             BiFunction<List<String>, Set<String>, Boolean> isSafeToKillAnyPackage =
198                     (packages, safeToKillPackages) -> {
199                         if (packages == null) {
200                             return false;
201                         }
202                         for (int i = 0; i < packages.size(); i++) {
203                             if (safeToKillPackages.contains(packages.get(i))) {
204                                 return true;
205                             }
206                         }
207                         return false;
208                     };
209 
210             switch (componentType) {
211                 case ComponentType.SYSTEM:
212                     if (mSafeToKillSystemPackages.contains(genericPackageName)) {
213                         return true;
214                     }
215                     return isSafeToKillAnyPackage.apply(sharedPackages, mSafeToKillSystemPackages);
216                 case ComponentType.VENDOR:
217                     if (mSafeToKillVendorPackages.contains(genericPackageName)) {
218                         return true;
219                     }
220                     /*
221                      * Packages under the vendor shared UID may contain system packages because when
222                      * CarWatchdogService derives the shared component type it attributes system
223                      * packages as vendor packages when there is at least one vendor package.
224                      */
225                     return isSafeToKillAnyPackage.apply(sharedPackages, mSafeToKillSystemPackages)
226                             || isSafeToKillAnyPackage.apply(sharedPackages,
227                             mSafeToKillVendorPackages);
228                 default:
229                     // Third-party apps are always killable
230                     return true;
231             }
232         }
233     }
234 
235     /** Returns the list of vendor package prefixes. */
getVendorPackagePrefixes()236     public List<String> getVendorPackagePrefixes() {
237         synchronized (mLock) {
238             return new ArrayList<>(mVendorPackagePrefixes);
239         }
240     }
241 
242     @GuardedBy("mLock")
clearLocked()243     private void clearLocked() {
244         mSafeToKillSystemPackages.clear();
245         mSafeToKillVendorPackages.clear();
246         mVendorPackagePrefixes.clear();
247         mPackagesByAppCategoryType.clear();
248         mGenericIoThresholdsByComponent.clear();
249         mIoThresholdsBySystemPackages.clear();
250         mIoThresholdsByVendorPackages.clear();
251         mIoThresholdsByAppCategoryType.clear();
252     }
253 
254     @GuardedBy("mLock")
setIoThresholdsLocked(int componentType, IoOveruseConfiguration ioConfig)255     private void setIoThresholdsLocked(int componentType, IoOveruseConfiguration ioConfig) {
256         mGenericIoThresholdsByComponent.append(componentType,
257                 ioConfig.componentLevelThresholds.perStateWriteBytes);
258         switch (componentType) {
259             case ComponentType.SYSTEM:
260                 populateThresholdsByPackagesLocked(
261                         ioConfig.packageSpecificThresholds, mIoThresholdsBySystemPackages);
262                 break;
263             case ComponentType.VENDOR:
264                 populateThresholdsByPackagesLocked(
265                         ioConfig.packageSpecificThresholds, mIoThresholdsByVendorPackages);
266                 setIoThresholdsByAppCategoryTypeLocked(ioConfig.categorySpecificThresholds);
267                 break;
268         }
269     }
270 
271     @GuardedBy("mLock")
setIoThresholdsByAppCategoryTypeLocked( List<PerStateIoOveruseThreshold> thresholds)272     private void setIoThresholdsByAppCategoryTypeLocked(
273             List<PerStateIoOveruseThreshold> thresholds) {
274         for (int i = 0; i < thresholds.size(); ++i) {
275             PerStateIoOveruseThreshold threshold = thresholds.get(i);
276             switch(threshold.name) {
277                 case INTERNAL_APPLICATION_CATEGORY_TYPE_MAPS:
278                     mIoThresholdsByAppCategoryType.append(
279                             ApplicationCategoryType.MAPS, threshold.perStateWriteBytes);
280                     break;
281                 case INTERNAL_APPLICATION_CATEGORY_TYPE_MEDIA:
282                     mIoThresholdsByAppCategoryType.append(ApplicationCategoryType.MEDIA,
283                             threshold.perStateWriteBytes);
284                     break;
285             }
286         }
287     }
288 
289     @GuardedBy("mLock")
populateThresholdsByPackagesLocked(List<PerStateIoOveruseThreshold> thresholds, ArrayMap<String, PerStateBytes> thresholdsByPackages)290     private void populateThresholdsByPackagesLocked(List<PerStateIoOveruseThreshold> thresholds,
291             ArrayMap<String, PerStateBytes> thresholdsByPackages) {
292         for (int i = 0; i < thresholds.size(); ++i) {
293             thresholdsByPackages.put(
294                     thresholds.get(i).name, thresholds.get(i).perStateWriteBytes);
295         }
296     }
297 
298     @GuardedBy("mLock")
fetchAppCategorySpecificThresholdLocked(String genericPackageName)299     private PerStateBytes fetchAppCategorySpecificThresholdLocked(String genericPackageName) {
300         for (int i = 0; i < mPackagesByAppCategoryType.size(); ++i) {
301             if (mPackagesByAppCategoryType.valueAt(i).contains(genericPackageName)) {
302                 return mIoThresholdsByAppCategoryType.get(mPackagesByAppCategoryType.keyAt(i));
303             }
304         }
305         return null;
306     }
307 
toApplicationCategoryTypeString(@pplicationCategoryType int type)308     private static String toApplicationCategoryTypeString(@ApplicationCategoryType int type) {
309         switch (type) {
310             case ApplicationCategoryType.MAPS:
311                 return "ApplicationCategoryType.MAPS";
312             case ApplicationCategoryType.MEDIA:
313                 return "ApplicationCategoryType.MEDIA";
314             case ApplicationCategoryType.OTHERS:
315                 return "ApplicationCategoryType.OTHERS";
316             default:
317                 return "Invalid ApplicationCategoryType";
318         }
319     }
320 
toComponentTypeString(@omponentType int type)321     private static String toComponentTypeString(@ComponentType int type) {
322         switch (type) {
323             case ComponentType.SYSTEM:
324                 return "ComponentType.SYSTEM";
325             case ComponentType.VENDOR:
326                 return "ComponentType.VENDOR";
327             case ComponentType.THIRD_PARTY:
328                 return "ComponentType.THIRD_PARTY";
329             default:
330                 return "ComponentType.UNKNOWN";
331         }
332     }
333 
dumpPerStateBytes(PerStateBytes perStateBytes, IndentingPrintWriter writer)334     private static void dumpPerStateBytes(PerStateBytes perStateBytes,
335             IndentingPrintWriter writer) {
336         if (perStateBytes == null) {
337             writer.println("{NULL}");
338             return;
339         }
340         writer.println("{Foreground bytes: " + perStateBytes.foregroundBytes
341                 + ", Background bytes: " + perStateBytes.backgroundBytes + ", Garage mode bytes: "
342                 + perStateBytes.garageModeBytes + '}');
343     }
344 
constructPerStateBytes(long fgBytes, long bgBytes, long gmBytes)345     private static PerStateBytes constructPerStateBytes(long fgBytes, long bgBytes, long gmBytes) {
346         return new PerStateBytes() {{
347                 foregroundBytes = fgBytes;
348                 backgroundBytes = bgBytes;
349                 garageModeBytes = gmBytes;
350             }};
351     }
352 
353     private static PerStateBytes copyPerStateBytes(PerStateBytes perStateBytes) {
354         return new PerStateBytes() {{
355                 foregroundBytes = perStateBytes.foregroundBytes;
356                 backgroundBytes = perStateBytes.backgroundBytes;
357                 garageModeBytes = perStateBytes.garageModeBytes;
358             }};
359     }
360 }
361