blob: 37fbbb3a7be9b1dc5cd68bfb1cd0f8a80b8fe98a [file] [log] [blame]
Lakshman Annadorai016127e2021-03-18 09:11:43 -07001/*
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
17package com.android.car.watchdog;
18
Lakshman Annadoraicf5f3a92021-04-02 15:26:16 -070019import static android.automotive.watchdog.internal.ResourceOveruseActionType.KILLED;
20import static android.automotive.watchdog.internal.ResourceOveruseActionType.KILLED_RECURRING_OVERUSE;
21import static android.automotive.watchdog.internal.ResourceOveruseActionType.NOT_KILLED;
22import static android.automotive.watchdog.internal.ResourceOveruseActionType.NOT_KILLED_USER_OPTED;
Lakshman Annadorai016127e2021-03-18 09:11:43 -070023import static android.car.watchdog.CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO;
Lakshman Annadoraicf5f3a92021-04-02 15:26:16 -070024import static android.car.watchdog.PackageKillableState.KILLABLE_STATE_NEVER;
25import static android.car.watchdog.PackageKillableState.KILLABLE_STATE_NO;
26import static android.car.watchdog.PackageKillableState.KILLABLE_STATE_YES;
27import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
28import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
29import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
30
31import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
Lakshman Annadorai016127e2021-03-18 09:11:43 -070032
33import android.annotation.NonNull;
Lakshman Annadoraicf5f3a92021-04-02 15:26:16 -070034import android.annotation.UserIdInt;
35import android.app.ActivityThread;
36import android.automotive.watchdog.ResourceType;
37import android.automotive.watchdog.internal.PackageIdentifier;
38import android.automotive.watchdog.internal.PackageIoOveruseStats;
39import android.automotive.watchdog.internal.PackageResourceOveruseAction;
Lakshman Annadorai016127e2021-03-18 09:11:43 -070040import android.car.watchdog.CarWatchdogManager;
41import android.car.watchdog.IResourceOveruseListener;
Lakshman Annadoraicf5f3a92021-04-02 15:26:16 -070042import android.car.watchdog.IoOveruseStats;
Lakshman Annadorai016127e2021-03-18 09:11:43 -070043import android.car.watchdog.PackageKillableState;
Lakshman Annadoraicf5f3a92021-04-02 15:26:16 -070044import android.car.watchdog.PackageKillableState.KillableState;
45import android.car.watchdog.PerStateBytes;
Lakshman Annadorai016127e2021-03-18 09:11:43 -070046import android.car.watchdog.ResourceOveruseConfiguration;
47import android.car.watchdog.ResourceOveruseStats;
Lakshman Annadoraicf5f3a92021-04-02 15:26:16 -070048import android.car.watchdoglib.CarWatchdogDaemonHelper;
49import android.content.Context;
50import android.content.pm.IPackageManager;
Lakshman Annadorai016127e2021-03-18 09:11:43 -070051import android.os.Binder;
Lakshman Annadoraicf5f3a92021-04-02 15:26:16 -070052import android.os.Handler;
Lakshman Annadorai016127e2021-03-18 09:11:43 -070053import android.os.IBinder;
Lakshman Annadoraicf5f3a92021-04-02 15:26:16 -070054import android.os.Looper;
Lakshman Annadorai016127e2021-03-18 09:11:43 -070055import android.os.RemoteException;
56import android.os.UserHandle;
Lakshman Annadoraicf5f3a92021-04-02 15:26:16 -070057import android.util.ArrayMap;
Lakshman Annadorai016127e2021-03-18 09:11:43 -070058import android.util.ArraySet;
59import android.util.IndentingPrintWriter;
60import android.util.SparseArray;
61
62import com.android.car.CarLog;
63import com.android.internal.annotations.GuardedBy;
Lakshman Annadoraicf5f3a92021-04-02 15:26:16 -070064import com.android.internal.annotations.VisibleForTesting;
Lakshman Annadorai016127e2021-03-18 09:11:43 -070065import com.android.internal.util.Preconditions;
66import com.android.server.utils.Slogf;
67
Lakshman Annadoraicf5f3a92021-04-02 15:26:16 -070068import java.time.ZoneOffset;
69import java.time.ZonedDateTime;
Lakshman Annadorai016127e2021-03-18 09:11:43 -070070import java.util.ArrayList;
71import java.util.List;
Lakshman Annadoraicf5f3a92021-04-02 15:26:16 -070072import java.util.Map;
Lakshman Annadorai016127e2021-03-18 09:11:43 -070073import java.util.Objects;
74import java.util.Set;
Lakshman Annadoraicf5f3a92021-04-02 15:26:16 -070075import java.util.function.BiFunction;
Lakshman Annadorai016127e2021-03-18 09:11:43 -070076
77/**
78 * Handles system resource performance monitoring module.
79 */
80public final class WatchdogPerfHandler {
81 private static final String TAG = CarLog.tagFor(CarWatchdogService.class);
82
83 private final boolean mIsDebugEnabled;
Lakshman Annadoraicf5f3a92021-04-02 15:26:16 -070084 private final Context mContext;
85 private final CarWatchdogDaemonHelper mCarWatchdogDaemonHelper;
86 private final PackageInfoHandler mPackageInfoHandler;
87 private final Handler mMainHandler;
Lakshman Annadorai016127e2021-03-18 09:11:43 -070088 private final Object mLock = new Object();
89 /*
90 * Cache of added resource overuse listeners by uid.
91 */
92 @GuardedBy("mLock")
Lakshman Annadoraicf5f3a92021-04-02 15:26:16 -070093 private final Map<String, PackageResourceUsage> mUsageByUserPackage = new ArrayMap<>();
94 @GuardedBy("mLock")
95 private final List<PackageResourceOveruseAction> mOveruseActionsByUserPackage =
96 new ArrayList<>();
97 @GuardedBy("mLock")
Lakshman Annadorai016127e2021-03-18 09:11:43 -070098 private final SparseArray<ResourceOveruseListenerInfo> mOveruseListenerInfosByUid =
99 new SparseArray<>();
100 @GuardedBy("mLock")
101 private final SparseArray<ResourceOveruseListenerInfo> mOveruseSystemListenerInfosByUid =
102 new SparseArray<>();
Lakshman Annadoraicf5f3a92021-04-02 15:26:16 -0700103 @GuardedBy("mLock")
104 private ZonedDateTime mLastStatsReportUTC;
Lakshman Annadorai016127e2021-03-18 09:11:43 -0700105
Lakshman Annadoraicf5f3a92021-04-02 15:26:16 -0700106 public WatchdogPerfHandler(Context context, CarWatchdogDaemonHelper daemonHelper,
107 PackageInfoHandler packageInfoHandler, boolean isDebugEnabled) {
Lakshman Annadorai016127e2021-03-18 09:11:43 -0700108 mIsDebugEnabled = isDebugEnabled;
Lakshman Annadoraicf5f3a92021-04-02 15:26:16 -0700109 mContext = context;
110 mCarWatchdogDaemonHelper = daemonHelper;
111 mPackageInfoHandler = packageInfoHandler;
112 mMainHandler = new Handler(Looper.getMainLooper());
113 mLastStatsReportUTC = ZonedDateTime.now(ZoneOffset.UTC);
Lakshman Annadorai016127e2021-03-18 09:11:43 -0700114 }
115
116 /** Initializes the handler. */
117 public void init() {
Lakshman Annadoraicf5f3a92021-04-02 15:26:16 -0700118 /*
119 * TODO(b/183947162): Opt-in to receive package change broadcast and handle package enabled
120 * state changes.
121 *
Lakshman Annadorai016127e2021-03-18 09:11:43 -0700122 * TODO(b/170741935): Read the current day's I/O overuse stats from database and push them
Lakshman Annadoraicf5f3a92021-04-02 15:26:16 -0700123 * to the daemon.
Lakshman Annadorai016127e2021-03-18 09:11:43 -0700124 */
Lakshman Annadoraicf5f3a92021-04-02 15:26:16 -0700125 synchronized (mLock) {
126 checkAndHandleDateChangeLocked();
127 }
Lakshman Annadorai016127e2021-03-18 09:11:43 -0700128 if (mIsDebugEnabled) {
129 Slogf.d(TAG, "WatchdogPerfHandler is initialized");
130 }
131 }
132
Lakshman Annadoraicf5f3a92021-04-02 15:26:16 -0700133 /** Releases the handler */
134 public void release() {
135 /*
136 * TODO(b/170741935): Write daily usage to SQLite DB storage.
137 */
138 if (mIsDebugEnabled) {
139 Slogf.d(TAG, "WatchdogPerfHandler is released");
140 }
141 }
142
Lakshman Annadorai016127e2021-03-18 09:11:43 -0700143 /** Dumps its state. */
144 public void dump(IndentingPrintWriter writer) {
145 /**
146 * TODO(b/170741935): Implement this method.
147 */
148 }
149
150 /** Returns resource overuse stats for the calling package. */
151 @NonNull
152 public ResourceOveruseStats getResourceOveruseStats(
153 @CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag,
154 @CarWatchdogManager.StatsPeriod int maxStatsPeriod) {
155 Preconditions.checkArgument((resourceOveruseFlag > 0),
156 "Must provide valid resource overuse flag");
157 Preconditions.checkArgument((maxStatsPeriod > 0),
158 "Must provide valid maximum stats period");
159 // TODO(b/170741935): Implement this method.
160 return new ResourceOveruseStats.Builder("",
161 UserHandle.getUserHandleForUid(Binder.getCallingUid())).build();
162 }
163
164 /** Returns resource overuse stats for all packages. */
165 @NonNull
166 public List<ResourceOveruseStats> getAllResourceOveruseStats(
167 @CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag,
168 @CarWatchdogManager.MinimumStatsFlag int minimumStatsFlag,
169 @CarWatchdogManager.StatsPeriod int maxStatsPeriod) {
170 Preconditions.checkArgument((resourceOveruseFlag > 0),
171 "Must provide valid resource overuse flag");
172 Preconditions.checkArgument((maxStatsPeriod > 0),
173 "Must provide valid maximum stats period");
174 // TODO(b/170741935): Implement this method.
175 return new ArrayList<>();
176 }
177
178 /** Returns resource overuse stats for the specified user package. */
179 @NonNull
180 public ResourceOveruseStats getResourceOveruseStatsForUserPackage(
181 @NonNull String packageName, @NonNull UserHandle userHandle,
182 @CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag,
183 @CarWatchdogManager.StatsPeriod int maxStatsPeriod) {
184 Objects.requireNonNull(packageName, "Package name must be non-null");
185 Objects.requireNonNull(userHandle, "User handle must be non-null");
186 Preconditions.checkArgument((resourceOveruseFlag > 0),
187 "Must provide valid resource overuse flag");
188 Preconditions.checkArgument((maxStatsPeriod > 0),
189 "Must provide valid maximum stats period");
190 // TODO(b/170741935): Implement this method.
191 return new ResourceOveruseStats.Builder("", userHandle).build();
192 }
193
194 /** Adds the resource overuse listener. */
195 public void addResourceOveruseListener(
196 @CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag,
197 @NonNull IResourceOveruseListener listener) {
198 Objects.requireNonNull(listener, "Listener must be non-null");
199 Preconditions.checkArgument((resourceOveruseFlag > 0),
200 "Must provide valid resource overuse flag");
201 synchronized (mLock) {
202 addResourceOveruseListenerLocked(resourceOveruseFlag, listener,
203 mOveruseListenerInfosByUid);
204 }
205 }
206
207 /** Removes the previously added resource overuse listener. */
208 public void removeResourceOveruseListener(@NonNull IResourceOveruseListener listener) {
209 Objects.requireNonNull(listener, "Listener must be non-null");
210 synchronized (mLock) {
211 removeResourceOveruseListenerLocked(listener, mOveruseListenerInfosByUid);
212 }
213 }
214
215 /** Adds the resource overuse system listener. */
216 public void addResourceOveruseListenerForSystem(
217 @CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag,
218 @NonNull IResourceOveruseListener listener) {
219 Objects.requireNonNull(listener, "Listener must be non-null");
220 Preconditions.checkArgument((resourceOveruseFlag > 0),
221 "Must provide valid resource overuse flag");
222 synchronized (mLock) {
223 addResourceOveruseListenerLocked(resourceOveruseFlag, listener,
224 mOveruseSystemListenerInfosByUid);
225 }
226 }
227
228 /** Removes the previously added resource overuse system listener. */
229 public void removeResourceOveruseListenerForSystem(@NonNull IResourceOveruseListener listener) {
230 Objects.requireNonNull(listener, "Listener must be non-null");
231 synchronized (mLock) {
232 removeResourceOveruseListenerLocked(listener, mOveruseSystemListenerInfosByUid);
233 }
234 }
235
236 /** Sets whether or not a package is killable on resource overuse. */
237 public void setKillablePackageAsUser(String packageName, UserHandle userHandle,
238 boolean isKillable) {
239 Objects.requireNonNull(packageName, "Package name must be non-null");
240 Objects.requireNonNull(userHandle, "User handle must be non-null");
241 /*
242 * TODO(b/170741935): Add/remove the package from the user do-no-kill list.
243 * If the {@code userHandle == UserHandle.ALL}, update the settings for all users.
244 */
245 }
246
247 /** Returns the list of package killable states on resource overuse for the user. */
248 @NonNull
249 public List<PackageKillableState> getPackageKillableStatesAsUser(UserHandle userHandle) {
250 Objects.requireNonNull(userHandle, "User handle must be non-null");
251 // TODO(b/170741935): Implement this method.
252 return new ArrayList<>();
253 }
254
255 /** Sets the given resource overuse configurations. */
256 public void setResourceOveruseConfigurations(
257 List<ResourceOveruseConfiguration> configurations,
258 @CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag) {
259 Objects.requireNonNull(configurations, "Configurations must be non-null");
260 Preconditions.checkArgument((resourceOveruseFlag > 0),
261 "Must provide valid resource overuse flag");
262 Set<Integer> seenComponentTypes = new ArraySet<>();
263 for (ResourceOveruseConfiguration config : configurations) {
264 int componentType = config.getComponentType();
265 if (seenComponentTypes.contains(componentType)) {
266 throw new IllegalArgumentException(
267 "Cannot provide duplicate configurations for the same component type");
268 }
269 if ((resourceOveruseFlag & FLAG_RESOURCE_OVERUSE_IO) != 0
270 && config.getIoOveruseConfiguration() == null) {
271 throw new IllegalArgumentException("Must provide I/O overuse configuration");
272 }
273 seenComponentTypes.add(config.getComponentType());
274 }
275 // TODO(b/170741935): Implement this method.
276 }
277
278 /** Returns the available resource overuse configurations. */
279 @NonNull
280 public List<ResourceOveruseConfiguration> getResourceOveruseConfigurations(
281 @CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag) {
282 Preconditions.checkArgument((resourceOveruseFlag > 0),
283 "Must provide valid resource overuse flag");
284 // TODO(b/170741935): Implement this method.
285 return new ArrayList<>();
286 }
287
Lakshman Annadoraicf5f3a92021-04-02 15:26:16 -0700288 /** Processes the latest I/O overuse stats */
289 public void latestIoOveruseStats(List<PackageIoOveruseStats> packageIoOveruseStats) {
290 int[] uids = new int[packageIoOveruseStats.size()];
291 for (int i = 0; i < packageIoOveruseStats.size(); ++i) {
292 uids[i] = packageIoOveruseStats.get(i).uid;
293 }
294 SparseArray<String> packageNamesByUid = mPackageInfoHandler.getPackageNamesForUids(uids);
295 synchronized (mLock) {
296 checkAndHandleDateChangeLocked();
297 for (PackageIoOveruseStats stats : packageIoOveruseStats) {
298 String packageName = packageNamesByUid.get(stats.uid, null);
299 if (packageName == null) {
300 continue;
301 }
302 int userId = UserHandle.getUserId(stats.uid);
303 PackageResourceUsage usage = cacheAndFetchUsageLocked(userId, packageName,
304 stats.ioOveruseStats);
305 if (stats.shouldNotify) {
306 /*
307 * Packages that exceed the warn threshold percentage should be notified as well
308 * and only the daemon is aware of such packages. Thus the flag is used to
309 * indicate which packages should be notified.
310 */
311 notifyResourceOveruseStatsLocked(stats.uid,
312 usage.getResourceOveruseStatsWithIo());
313 }
314 if (!usage.ioUsage.exceedsThreshold()) {
315 continue;
316 }
317 PackageResourceOveruseAction overuseAction = new PackageResourceOveruseAction();
318 overuseAction.packageIdentifier = new PackageIdentifier();
319 overuseAction.packageIdentifier.name = packageName;
320 overuseAction.packageIdentifier.uid = stats.uid;
321 overuseAction.resourceTypes = new int[]{ ResourceType.IO };
322 overuseAction.resourceOveruseActionType = NOT_KILLED;
323 /*
324 * No action required on I/O overuse on one of the following cases:
325 * #1 The package is not safe to kill as it is critical for system stability.
326 * #2 The package has no recurring overuse behavior and the user opted to not
327 * kill the package so honor the user's decision.
328 */
329 String userPackageId = getUserPackageUniqueId(userId, packageName);
330 int killableState = usage.getKillableState();
331 if (killableState == KILLABLE_STATE_NEVER) {
332 mOveruseActionsByUserPackage.add(overuseAction);
333 continue;
334 }
335 boolean hasRecurringOveruse = isRecurringOveruseLocked(usage);
336 if (!hasRecurringOveruse && killableState == KILLABLE_STATE_NO) {
337 overuseAction.resourceOveruseActionType = NOT_KILLED_USER_OPTED;
338 mOveruseActionsByUserPackage.add(overuseAction);
339 continue;
340 }
341 try {
342 int oldEnabledState = -1;
343 IPackageManager packageManager = ActivityThread.getPackageManager();
344 if (!hasRecurringOveruse) {
345 oldEnabledState = packageManager.getApplicationEnabledSetting(packageName,
346 userId);
347
348 if (oldEnabledState == COMPONENT_ENABLED_STATE_DISABLED
349 || oldEnabledState == COMPONENT_ENABLED_STATE_DISABLED_USER
350 || oldEnabledState == COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
351 mOveruseActionsByUserPackage.add(overuseAction);
352 continue;
353 }
354 }
355
356 packageManager.setApplicationEnabledSetting(packageName,
357 COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED, /* flags= */ 0, userId,
358 mContext.getPackageName());
359
360 overuseAction.resourceOveruseActionType = hasRecurringOveruse
361 ? KILLED_RECURRING_OVERUSE : KILLED;
362 if (!hasRecurringOveruse) {
363 usage.oldEnabledState = oldEnabledState;
364 }
365 } catch (RemoteException e) {
366 Slogf.e(TAG, "Failed to disable application enabled setting for user %d, "
367 + "package '%s'", userId, packageName);
368 }
369 mOveruseActionsByUserPackage.add(overuseAction);
370 }
371 if (!mOveruseActionsByUserPackage.isEmpty()) {
372 mMainHandler.sendMessage(obtainMessage(
373 WatchdogPerfHandler::notifyActionsTakenOnOveruse, this));
374 }
375 }
376 }
377
378 /** Notify daemon about the actions take on resource overuse */
379 public void notifyActionsTakenOnOveruse() {
380 List<PackageResourceOveruseAction> actions;
381 synchronized (mLock) {
382 if (mOveruseActionsByUserPackage.isEmpty()) {
383 return;
384 }
385 actions = new ArrayList<>(mOveruseActionsByUserPackage);
386 mOveruseActionsByUserPackage.clear();
387 }
388 try {
389 mCarWatchdogDaemonHelper.actionTakenOnResourceOveruse(actions);
390 } catch (RemoteException e) {
391 Slogf.w(TAG, "Failed to notify car watchdog daemon of actions taken on "
392 + "resource overuse: %s", e);
393 }
394 }
395
396 private void notifyResourceOveruseStatsLocked(int uid,
397 ResourceOveruseStats resourceOveruseStats) {
398 String packageName = resourceOveruseStats.getPackageName();
399 ResourceOveruseListenerInfo listenerInfo = mOveruseListenerInfosByUid.get(uid, null);
400 if (listenerInfo != null && (listenerInfo.flag & FLAG_RESOURCE_OVERUSE_IO) != 0) {
401 try {
402 listenerInfo.listener.onOveruse(resourceOveruseStats);
403 } catch (RemoteException e) {
404 Slogf.e(TAG,
405 "Failed to notify listener(uid %d, package '%s') on resource overuse: %s",
406 uid, resourceOveruseStats, e);
407 }
408 }
409 for (int i = 0; i < mOveruseSystemListenerInfosByUid.size(); ++i) {
410 ResourceOveruseListenerInfo systemListenerInfo =
411 mOveruseSystemListenerInfosByUid.valueAt(i);
412 if ((systemListenerInfo.flag & FLAG_RESOURCE_OVERUSE_IO) == 0) {
413 continue;
414 }
415 try {
416 systemListenerInfo.listener.onOveruse(resourceOveruseStats);
417 } catch (RemoteException e) {
418 Slogf.e(TAG, "Failed to notify system listener(uid %d, pid: %d) of resource "
419 + "overuse by package(uid %d, package '%s'): %s",
420 systemListenerInfo.uid, systemListenerInfo.pid, uid, packageName, e);
421 }
422 }
423 }
424
425 private void checkAndHandleDateChangeLocked() {
426 ZonedDateTime previousUTC = mLastStatsReportUTC;
427 mLastStatsReportUTC = ZonedDateTime.now(ZoneOffset.UTC);
428 if (mLastStatsReportUTC.getDayOfYear() == previousUTC.getDayOfYear()
429 && mLastStatsReportUTC.getYear() == previousUTC.getYear()) {
430 return;
431 }
432 for (PackageResourceUsage usage : mUsageByUserPackage.values()) {
433 if (usage.oldEnabledState > 0) {
434 // Forgive the daily disabled package on date change.
435 try {
436 IPackageManager packageManager = ActivityThread.getPackageManager();
437 if (packageManager.getApplicationEnabledSetting(usage.packageName,
438 usage.userId)
439 != COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
440 continue;
441 }
442 packageManager.setApplicationEnabledSetting(usage.packageName,
443 usage.oldEnabledState,
444 /* flags= */ 0, usage.userId, mContext.getPackageName());
445 } catch (RemoteException e) {
446 Slogf.e(TAG,
447 "Failed to reset enabled setting for disabled package '%s', user %d",
448 usage.packageName, usage.userId);
449 }
450 }
451 /* TODO(b/170741935): Stash the old usage into SQLite DB storage. */
452 usage.ioUsage.clear();
453 }
454 }
455
456 private PackageResourceUsage cacheAndFetchUsageLocked(@UserIdInt int userId, String packageName,
457 android.automotive.watchdog.IoOveruseStats internalStats) {
458 String key = getUserPackageUniqueId(userId, packageName);
459 PackageResourceUsage usage = mUsageByUserPackage.getOrDefault(key,
460 new PackageResourceUsage(userId, packageName));
461 usage.update(internalStats);
462 mUsageByUserPackage.put(key, usage);
463 return usage;
464 }
465
466 private boolean isRecurringOveruseLocked(PackageResourceUsage ioUsage) {
467 /*
468 * TODO(b/170741935): Look up I/O overuse history and determine whether or not the package
469 * has recurring I/O overuse behavior.
470 */
471 return false;
472 }
473
Lakshman Annadorai016127e2021-03-18 09:11:43 -0700474 private void addResourceOveruseListenerLocked(
475 @CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag,
476 @NonNull IResourceOveruseListener listener,
477 SparseArray<ResourceOveruseListenerInfo> listenerInfosByUid) {
478 int callingPid = Binder.getCallingPid();
479 int callingUid = Binder.getCallingUid();
480 boolean isListenerForSystem = listenerInfosByUid == mOveruseSystemListenerInfosByUid;
481 String listenerType = isListenerForSystem ? "resource overuse listener for system" :
482 "resource overuse listener";
483
484 ResourceOveruseListenerInfo existingListenerInfo = listenerInfosByUid.get(callingUid, null);
485 if (existingListenerInfo != null) {
486 IBinder binder = listener.asBinder();
487 if (existingListenerInfo.listener.asBinder() == binder) {
488 throw new IllegalStateException(
489 "Cannot add " + listenerType + " as it is already added");
490 }
491 }
492
493 ResourceOveruseListenerInfo listenerInfo = new ResourceOveruseListenerInfo(listener,
494 resourceOveruseFlag, callingPid, callingUid, isListenerForSystem);
495 try {
496 listenerInfo.linkToDeath();
497 } catch (RemoteException e) {
498 Slogf.w(TAG, "Cannot add %s: linkToDeath to listener failed", listenerType);
499 return;
500 }
501
502 if (existingListenerInfo != null) {
503 Slogf.w(TAG, "Overwriting existing %s: pid %d, uid: %d", listenerType,
504 existingListenerInfo.pid, existingListenerInfo.uid);
505 existingListenerInfo.unlinkToDeath();
506 }
507
508
509 listenerInfosByUid.put(callingUid, listenerInfo);
510 if (mIsDebugEnabled) {
511 Slogf.d(TAG, "The %s (pid: %d, uid: %d) is added", listenerType,
512 callingPid, callingUid);
513 }
514 }
515
516 private void removeResourceOveruseListenerLocked(@NonNull IResourceOveruseListener listener,
517 SparseArray<ResourceOveruseListenerInfo> listenerInfosByUid) {
518 int callingUid = Binder.getCallingUid();
519
520 String listenerType = listenerInfosByUid == mOveruseSystemListenerInfosByUid
521 ? "resource overuse system listener" : "resource overuse listener";
522
523 ResourceOveruseListenerInfo listenerInfo = listenerInfosByUid.get(callingUid, null);
524 if (listenerInfo == null || listenerInfo.listener != listener) {
525 Slogf.w(TAG, "Cannot remove the %s: it has not been registered before", listenerType);
526 return;
527 }
528 listenerInfo.unlinkToDeath();
529 listenerInfosByUid.remove(callingUid);
530 if (mIsDebugEnabled) {
531 Slogf.d(TAG, "The %s (pid: %d, uid: %d) is removed", listenerType, listenerInfo.pid,
532 listenerInfo.uid);
533 }
534 }
535
536 private void onResourceOveruseListenerDeath(int uid, boolean isListenerForSystem) {
537 synchronized (mLock) {
538 if (isListenerForSystem) {
539 mOveruseSystemListenerInfosByUid.remove(uid);
540 } else {
541 mOveruseListenerInfosByUid.remove(uid);
542 }
543 }
544 }
545
Lakshman Annadoraicf5f3a92021-04-02 15:26:16 -0700546 private static String getUserPackageUniqueId(int userId, String packageName) {
547 return String.valueOf(userId) + ":" + packageName;
548 }
549
550 @VisibleForTesting
551 static IoOveruseStats.Builder toIoOveruseStatsBuilder(
552 android.automotive.watchdog.IoOveruseStats internalStats) {
553 IoOveruseStats.Builder statsBuilder = new IoOveruseStats.Builder(
554 internalStats.startTime, internalStats.durationInSeconds);
555 statsBuilder.setRemainingWriteBytes(
556 toPerStateBytes(internalStats.remainingWriteBytes));
557 statsBuilder.setTotalBytesWritten(totalPerStateBytes(internalStats.writtenBytes));
558 statsBuilder.setTotalOveruses(internalStats.totalOveruses);
559 return statsBuilder;
560 }
561
562 private static PerStateBytes toPerStateBytes(
563 android.automotive.watchdog.PerStateBytes internalPerStateBytes) {
564 PerStateBytes perStateBytes = new PerStateBytes(internalPerStateBytes.foregroundBytes,
565 internalPerStateBytes.backgroundBytes, internalPerStateBytes.garageModeBytes);
566 return perStateBytes;
567 }
568
569 private static long totalPerStateBytes(
570 android.automotive.watchdog.PerStateBytes internalPerStateBytes) {
571 BiFunction<Long, Long, Long> sum = (l, r) -> {
572 return (Long.MAX_VALUE - l > r) ? l + r : Long.MAX_VALUE;
573 };
574 return sum.apply(sum.apply(internalPerStateBytes.foregroundBytes,
575 internalPerStateBytes.backgroundBytes), internalPerStateBytes.garageModeBytes);
576 }
577
578 private static final class PackageResourceUsage {
579 public final String packageName;
580 public @UserIdInt final int userId;
581 public final PackageIoUsage ioUsage;
582 public int oldEnabledState;
583
584 private @KillableState int mKillableState;
585
586 PackageResourceUsage(@UserIdInt int userId, String packageName) {
587 this.packageName = packageName;
588 this.userId = userId;
589 this.ioUsage = new PackageIoUsage();
590 this.oldEnabledState = -1;
591 this.mKillableState = KILLABLE_STATE_YES;
592 }
593
594 public void update(android.automotive.watchdog.IoOveruseStats internalStats) {
595 if (!internalStats.killableOnOveruse) {
596 /*
597 * Killable value specified in the internal stats is provided by the native daemon.
598 * This value reflects whether or not an application is safe-to-kill on overuse.
599 * This setting is from the I/O overuse configuration specified by the system and
600 * vendor services and doesn't reflect the user choices. Thus if the internal stats
601 * specify the application is not killable, the application is not safe-to-kill.
602 */
603 this.mKillableState = KILLABLE_STATE_NEVER;
604 }
605 ioUsage.update(internalStats);
606 }
607
608 public ResourceOveruseStats getResourceOveruseStatsWithIo() {
609 IoOveruseStats ioOveruseStats = null;
610 if (ioUsage.hasUsage()) {
611 ioOveruseStats = ioUsage.getStatsBuilder().setKillableOnOveruse(
612 mKillableState != PackageKillableState.KILLABLE_STATE_NEVER).build();
613 }
614
615 return new ResourceOveruseStats.Builder(packageName, UserHandle.of(userId))
616 .setIoOveruseStats(ioOveruseStats).build();
617 }
618
619 public @KillableState int getKillableState() {
620 return mKillableState;
621 }
622
623 public boolean setKillableState(boolean isKillable) {
624 if (mKillableState == PackageKillableState.KILLABLE_STATE_NEVER) {
625 return false;
626 }
627 mKillableState = isKillable ? KILLABLE_STATE_YES : KILLABLE_STATE_NO;
628 return true;
629 }
630 }
631
632 private static final class PackageIoUsage {
633 private android.automotive.watchdog.IoOveruseStats mIoOveruseStats;
634 private android.automotive.watchdog.PerStateBytes mForgivenWriteBytes;
635 private long mTotalTimesKilled;
636
637 PackageIoUsage() {
638 mTotalTimesKilled = 0;
639 }
640
641 public boolean hasUsage() {
642 return mIoOveruseStats != null;
643 }
644
645 public void update(android.automotive.watchdog.IoOveruseStats internalStats) {
646 mIoOveruseStats = internalStats;
647 if (exceedsThreshold()) {
648 /*
649 * Forgive written bytes on overuse as the package is either forgiven or killed on
650 * overuse. When the package is killed, the user may opt to open the corresponding
651 * app and the package should be forgiven anyways.
652 * NOTE: If this logic is updated, update the daemon side logic as well.
653 */
654 mForgivenWriteBytes = internalStats.writtenBytes;
655 }
656 }
657
658 public IoOveruseStats.Builder getStatsBuilder() {
659 IoOveruseStats.Builder statsBuilder = toIoOveruseStatsBuilder(mIoOveruseStats);
660 statsBuilder.setTotalTimesKilled(mTotalTimesKilled);
661 return statsBuilder;
662 }
663
664 public boolean exceedsThreshold() {
665 if (!hasUsage()) {
666 return false;
667 }
668 android.automotive.watchdog.PerStateBytes remaining =
669 mIoOveruseStats.remainingWriteBytes;
670 return remaining.foregroundBytes == 0 || remaining.backgroundBytes == 0
671 || remaining.garageModeBytes == 0;
672 }
673
674 public void clear() {
675 mIoOveruseStats = null;
676 mForgivenWriteBytes = null;
677 mTotalTimesKilled = 0;
678 }
679 }
680
Lakshman Annadorai016127e2021-03-18 09:11:43 -0700681 private final class ResourceOveruseListenerInfo implements IBinder.DeathRecipient {
682 public final IResourceOveruseListener listener;
683 public final @CarWatchdogManager.ResourceOveruseFlag int flag;
684 public final int pid;
685 public final int uid;
686 public final boolean isListenerForSystem;
687
688 ResourceOveruseListenerInfo(IResourceOveruseListener listener,
689 @CarWatchdogManager.ResourceOveruseFlag int flag, int pid, int uid,
690 boolean isListenerForSystem) {
691 this.listener = listener;
692 this.flag = flag;
693 this.pid = pid;
694 this.uid = uid;
695 this.isListenerForSystem = isListenerForSystem;
696 }
697
698 @Override
699 public void binderDied() {
700 Slogf.w(TAG, "Resource overuse listener%s (pid: %d) died",
701 isListenerForSystem ? " for system" : "", pid);
702 onResourceOveruseListenerDeath(uid, isListenerForSystem);
703 unlinkToDeath();
704 }
705
706 private void linkToDeath() throws RemoteException {
707 listener.asBinder().linkToDeath(this, 0);
708 }
709
710 private void unlinkToDeath() {
711 listener.asBinder().unlinkToDeath(this, 0);
712 }
713 }
714}