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.server.scheduling; 18 19 import static com.android.server.scheduling.SchedulingStatsLog.LONG_REBOOT_BLOCKING_REPORTED; 20 import static com.android.server.scheduling.SchedulingStatsLog.LONG_REBOOT_BLOCKING_REPORTED__REBOOT_BLOCK_REASON__APP_UID; 21 import static com.android.server.scheduling.SchedulingStatsLog.LONG_REBOOT_BLOCKING_REPORTED__REBOOT_BLOCK_REASON__SYSTEM_COMPONENT; 22 23 import android.annotation.CurrentTimeMillisLong; 24 import android.annotation.IntDef; 25 import android.content.ApexEnvironment; 26 import android.content.Context; 27 import android.content.pm.PackageManager; 28 import android.os.SystemClock; 29 import android.util.ArrayMap; 30 import android.util.AtomicFile; 31 import android.util.Log; 32 import android.util.SparseArray; 33 34 import com.android.internal.annotations.GuardedBy; 35 import com.android.internal.annotations.VisibleForTesting; 36 37 import java.io.File; 38 import java.io.FileInputStream; 39 import java.io.FileOutputStream; 40 import java.io.PrintWriter; 41 import java.lang.annotation.Retention; 42 import java.lang.annotation.RetentionPolicy; 43 import java.util.ArrayList; 44 import java.util.List; 45 46 47 /** 48 * Class for handling the storage of logging information related to reboot readiness. 49 */ 50 final class RebootReadinessLogger { 51 private static final String TAG = "RebootReadinessLogger"; 52 53 private static final String MODULE_NAME = "com.android.scheduling"; 54 55 private static final String REBOOT_STATS_FILE = "reboot-readiness/reboot-stats.xml"; 56 57 private final Object mLock = new Object(); 58 59 @GuardedBy("mLock") 60 private final SparseArray<BlockingEntityRecord> mBlockingApps = new SparseArray<>(); 61 62 @GuardedBy("mLock") 63 private final ArrayMap<String, BlockingEntityRecord> mBlockingComponents = new ArrayMap<>(); 64 65 @GuardedBy("mLock") 66 private long mStartTime; 67 68 @GuardedBy("mLock") 69 private long mReadyTime; 70 71 @GuardedBy("mLock") 72 private int mTimesBlockedByInteractivity; 73 74 @GuardedBy("mLock") 75 private int mTimesBlockedBySubsystems; 76 77 @GuardedBy("mLock") 78 private int mTimesBlockedByAppActivity; 79 80 @GuardedBy("mLock") 81 private long mTimeUntilNextInteractionMs; 82 83 @GuardedBy("mLock") 84 private boolean mNeedsToLogMetrics; 85 86 @GuardedBy("mLock") 87 private boolean mShouldDumpMetrics; 88 89 // Directory for storing reboot readiness metrics. By default, this is the module's DE 90 // data directory, but a different directory may be injected for testing purposes. 91 private final File mDeDir; 92 93 private final Context mContext; 94 95 private final PackageManager mPackageManager; 96 97 @VisibleForTesting RebootReadinessLogger(File deDir, Context context)98 RebootReadinessLogger(File deDir, Context context) { 99 mDeDir = deDir; 100 mContext = context; 101 mPackageManager = mContext.getPackageManager(); 102 } 103 RebootReadinessLogger(Context context)104 RebootReadinessLogger(Context context) { 105 mDeDir = ApexEnvironment.getApexEnvironment(MODULE_NAME).getDeviceProtectedDataDir(); 106 mContext = context; 107 mPackageManager = mContext.getPackageManager(); 108 } 109 110 /** 111 * Persists important information about recent reboot readiness checks. This information is 112 * stored when the device first becomes reboot-ready. 113 * 114 * @param startTime the time the reboot readiness state started to be polled 115 * @param readyTime the time the device became reboot-ready 116 * @param timesBlockedByInteractivity the number of times the reboot was blocked by device 117 * interactivity 118 * @param timesBlockedBySubsystems the number of times the reboot was blocked by subsystems 119 * that registered callbacks 120 * @param timesBlockedByAppActivity the number of times the reboot was blocked by background 121 * app activity 122 */ writeAfterRebootReadyBroadcast(@urrentTimeMillisLong long startTime, @CurrentTimeMillisLong long readyTime, int timesBlockedByInteractivity, int timesBlockedBySubsystems, int timesBlockedByAppActivity)123 void writeAfterRebootReadyBroadcast(@CurrentTimeMillisLong long startTime, 124 @CurrentTimeMillisLong long readyTime, int timesBlockedByInteractivity, 125 int timesBlockedBySubsystems, int timesBlockedByAppActivity) { 126 synchronized (mLock) { 127 AtomicFile rebootStatsFile = new AtomicFile(new File(mDeDir, REBOOT_STATS_FILE)); 128 129 mStartTime = startTime; 130 mReadyTime = readyTime; 131 mTimesBlockedByInteractivity = timesBlockedByInteractivity; 132 mTimesBlockedBySubsystems = timesBlockedBySubsystems; 133 mTimesBlockedByAppActivity = timesBlockedByAppActivity; 134 mShouldDumpMetrics = true; 135 136 RebootStats rebootStats = new RebootStats(); 137 rebootStats.setStartTimeMs(startTime); 138 rebootStats.setReadyTimeMs(readyTime); 139 rebootStats.setTimesBlockedByInteractivity(timesBlockedByInteractivity); 140 rebootStats.setTimesBlockedBySubsystems(timesBlockedBySubsystems); 141 rebootStats.setTimesBlockedByAppActivity(timesBlockedByAppActivity); 142 writeRebootStatsToFile(rebootStats, rebootStatsFile); 143 } 144 } 145 146 /** 147 * Deletes any information that would have been logged post-reboot. This is called when 148 * the device becomes not ready to reboot. 149 */ deleteLoggingInformation()150 void deleteLoggingInformation() { 151 synchronized (mLock) { 152 AtomicFile rebootStatsFile = getRebootStatsFile(); 153 if (rebootStatsFile != null) { 154 rebootStatsFile.delete(); 155 mNeedsToLogMetrics = false; 156 } 157 } 158 } 159 160 /** 161 * Amend logging file when the device becomes not ready to reboot. If a logging file exists, 162 * add information about the time between the device becoming ready to reboot and subsequently 163 * not ready to reboot. 164 */ writeAfterNotRebootReadyBroadcast()165 void writeAfterNotRebootReadyBroadcast() { 166 synchronized (mLock) { 167 AtomicFile rebootStatsFile = getRebootStatsFile(); 168 if (rebootStatsFile != null) { 169 RebootStats rebootStats; 170 try (FileInputStream stream = rebootStatsFile.openRead()) { 171 rebootStats = XmlParser.read(stream); 172 } catch (Exception e) { 173 Log.e(TAG, "Could not read reboot readiness stats: " + e); 174 return; 175 } 176 177 mTimeUntilNextInteractionMs = System.currentTimeMillis() - mReadyTime; 178 rebootStats.setTimeUntilNextInteractionMs(mTimeUntilNextInteractionMs); 179 writeRebootStatsToFile(rebootStats, rebootStatsFile); 180 } 181 182 } 183 } 184 185 /** 186 * If any metrics were stored before the last reboot, reads them into local variables. These 187 * local variables will be logged when the device is first unlocked after reboot. 188 */ readMetricsPostReboot()189 void readMetricsPostReboot() { 190 synchronized (mLock) { 191 AtomicFile rebootStatsFile = getRebootStatsFile(); 192 if (rebootStatsFile != null) { 193 try (FileInputStream stream = rebootStatsFile.openRead()) { 194 RebootStats rebootStats = XmlParser.read(stream); 195 mReadyTime = rebootStats.getReadyTimeMs(); 196 mStartTime = rebootStats.getStartTimeMs(); 197 mTimesBlockedByInteractivity = rebootStats.getTimesBlockedByInteractivity(); 198 mTimesBlockedBySubsystems = rebootStats.getTimesBlockedBySubsystems(); 199 mTimesBlockedByAppActivity = rebootStats.getTimesBlockedByAppActivity(); 200 if (rebootStats.hasTimeUntilNextInteractionMs()) { 201 mTimeUntilNextInteractionMs = rebootStats.getTimeUntilNextInteractionMs(); 202 } 203 mNeedsToLogMetrics = true; 204 } catch (Exception e) { 205 Log.e(TAG, "Could not read reboot readiness stats: " + e); 206 } finally { 207 rebootStatsFile.delete(); 208 } 209 } 210 } 211 } 212 213 /** 214 * Logs metrics which have been stored across reboots, if any exist. This method will be called 215 * after the first time the device is unlocked after reboot. 216 */ writePostRebootMetrics()217 void writePostRebootMetrics() { 218 synchronized (mLock) { 219 if (mNeedsToLogMetrics) { 220 mNeedsToLogMetrics = false; 221 long timeToUnlockMs = SystemClock.elapsedRealtime(); 222 long timeToRebootReadyMs = mReadyTime - mStartTime; 223 long timeToNextInteractionMs = System.currentTimeMillis() - mReadyTime; 224 if (mTimeUntilNextInteractionMs != 0) { 225 timeToNextInteractionMs = mTimeUntilNextInteractionMs; 226 } 227 Log.i(TAG, "UnattendedRebootOccurred" 228 + " rebootReadyMs=" + timeToRebootReadyMs 229 + " timeUntilFirstUnlockMs=" + timeToUnlockMs 230 + " blockedByInteractivity=" + mTimesBlockedByInteractivity 231 + " blockedBySubsystems=" + mTimesBlockedBySubsystems 232 + " blockedByAppActivity=" + mTimesBlockedByAppActivity 233 + " timeToNextInteractionMs=" + timeToNextInteractionMs); 234 SchedulingStatsLog.write(SchedulingStatsLog.UNATTENDED_REBOOT_OCCURRED, 235 timeToRebootReadyMs, 236 timeToUnlockMs, 237 mTimesBlockedByAppActivity, 238 mTimesBlockedBySubsystems, 239 mTimesBlockedByInteractivity, 240 timeToNextInteractionMs); 241 mShouldDumpMetrics = true; 242 } 243 } 244 } 245 246 /** 247 * Tracks any components which are currently blocking the reboot. If any of the components have 248 * been blocking the reboot for longer than the given threshold, this information will be logged 249 * to SchedulingStatsLog. Any components which previously blocked the reboot but are currently 250 * not blocking the reboot will be pruned from the set of tracked components. 251 * 252 * @param blockingComponentNames list of component names which are blocking the reboot. 253 * @param thresholdMs the time a component may block the reboot for before being logged. 254 */ maybeLogLongBlockingComponents(List<String> blockingComponentNames, long thresholdMs)255 void maybeLogLongBlockingComponents(List<String> blockingComponentNames, long thresholdMs) { 256 synchronized (mLock) { 257 for (String component : blockingComponentNames) { 258 BlockingEntityRecord record = mBlockingComponents.get(component); 259 if (record == null) { 260 record = new BlockingEntityRecord(component); 261 mBlockingComponents.put(component, record); 262 } 263 record.logLongRebootBlockingIfNecessary(thresholdMs); 264 } 265 266 for (String existingRecordName : mBlockingComponents.keySet()) { 267 if (!blockingComponentNames.contains(existingRecordName)) { 268 mBlockingComponents.remove(existingRecordName); 269 } 270 } 271 } 272 } 273 274 /** 275 * Tracks any app uids which are currently blocking the reboot. If any of the apps have been 276 * blocking the reboot for longer than the given threshold, this information will be logged 277 * to SchedulingStatsLog. Any apps which previously blocked the reboot but are currently 278 * not blocking the reboot will be pruned from the set of tracked uids. 279 * 280 * @param blockingAppUids list of app uids which are blocking the reboot. 281 * @param thresholdMs the time an app may block the reboot for before being logged. 282 * 283 * TODO(b/184165442): Use IntArray instead. 284 */ maybeLogLongBlockingApps(List<Integer> blockingAppUids, long thresholdMs)285 void maybeLogLongBlockingApps(List<Integer> blockingAppUids, long thresholdMs) { 286 synchronized (mLock) { 287 for (Integer uid : blockingAppUids) { 288 BlockingEntityRecord record = mBlockingApps.get(uid); 289 if (record == null) { 290 record = new BlockingEntityRecord(uid); 291 mBlockingApps.put(uid, record); 292 } 293 record.logLongRebootBlockingIfNecessary(thresholdMs); 294 } 295 296 List<Integer> uidsToPrune = new ArrayList<>(); 297 for (int i = 0; i < mBlockingApps.size(); i++) { 298 int uid = mBlockingApps.keyAt(i); 299 if (!blockingAppUids.contains(uid)) { 300 uidsToPrune.add(uid); 301 } 302 } 303 304 for (Integer uid : uidsToPrune) { 305 mBlockingApps.remove(uid); 306 } 307 } 308 } 309 310 @VisibleForTesting getRebootStatsFile()311 AtomicFile getRebootStatsFile() { 312 File file = new File(mDeDir, REBOOT_STATS_FILE); 313 if (file.exists()) { 314 return new AtomicFile(new File(mDeDir, REBOOT_STATS_FILE)); 315 } else { 316 return null; 317 } 318 } 319 320 /** Writes information about any UIDs which are blocking the reboot. */ writeBlockingUids(PrintWriter pw)321 void writeBlockingUids(PrintWriter pw) { 322 synchronized (mLock) { 323 for (int i = 0; i < mBlockingApps.size(); i++) { 324 int appUid = mBlockingApps.valueAt(i).mAppUid; 325 String[] pkgs = mPackageManager.getPackagesForUid(appUid); 326 pw.println("Blocking uid: " + appUid + ", package(s): " + String.join(",", pkgs)); 327 } 328 } 329 } 330 331 /** Writes information about any subsystems which are blocking the reboot. */ writeBlockingSubsystems(PrintWriter pw)332 void writeBlockingSubsystems(PrintWriter pw) { 333 synchronized (mLock) { 334 for (String subsystem : mBlockingComponents.keySet()) { 335 pw.println("Blocking subsystem: " + subsystem); 336 } 337 } 338 } 339 writeRebootStatsToFile(RebootStats rebootStats, AtomicFile rebootStatsFile)340 private void writeRebootStatsToFile(RebootStats rebootStats, AtomicFile rebootStatsFile) { 341 FileOutputStream stream = null; 342 try { 343 stream = rebootStatsFile.startWrite(); 344 PrintWriter pw = new PrintWriter(stream); 345 XmlWriter writer = new XmlWriter(pw); 346 XmlWriter.write(writer, rebootStats); 347 pw.flush(); 348 rebootStatsFile.finishWrite(stream); 349 } catch (Exception e) { 350 Log.e(TAG, "Could not write reboot readiness stats: " + e); 351 rebootStatsFile.failWrite(stream); 352 } 353 } 354 dump(PrintWriter pw)355 void dump(PrintWriter pw) { 356 synchronized (mLock) { 357 if (mBlockingComponents.size() > 0) { 358 writeBlockingSubsystems(pw); 359 } 360 361 if (mBlockingApps.size() > 0) { 362 writeBlockingUids(pw); 363 } 364 365 if (mShouldDumpMetrics) { 366 pw.println("Previous reboot readiness checks:"); 367 pw.println(" Start timestamp: " + mStartTime); 368 pw.println(" Ready timestamp: " + mReadyTime); 369 pw.println(" Times blocked by interactivity " + mTimesBlockedByInteractivity); 370 pw.println(" Times blocked by subsystems " + mTimesBlockedBySubsystems); 371 pw.println(" Times blocked by app activity " + mTimesBlockedByAppActivity); 372 pw.println(" Time until next interaction " + mTimeUntilNextInteractionMs); 373 } 374 } 375 } 376 377 378 /** 379 * Class for tracking system components or app uids which are blocking the reboot. Handles 380 * the tracking of how long an entity has blocked the reboot for, and handles the logging 381 * of LongRebootBlockingReported events to SchedulingStatsLog. 382 */ 383 private static final class BlockingEntityRecord { 384 385 private final int mType; 386 private String mComponentName; 387 private int mAppUid; 388 @CurrentTimeMillisLong private long mLastMetricLoggedTime; 389 390 @Retention(RetentionPolicy.SOURCE) 391 @IntDef({ 392 ENTITY_TYPE_COMPONENT, 393 ENTITY_TYPE_APP, 394 }) 395 private @interface EntityType {} 396 397 private static final int ENTITY_TYPE_COMPONENT = 1; 398 private static final int ENTITY_TYPE_APP = 2; 399 BlockingEntityRecord(String name)400 private BlockingEntityRecord(String name) { 401 mType = ENTITY_TYPE_COMPONENT; 402 mComponentName = name; 403 mLastMetricLoggedTime = System.currentTimeMillis(); 404 } 405 BlockingEntityRecord(int uid)406 private BlockingEntityRecord(int uid) { 407 mType = ENTITY_TYPE_APP; 408 mComponentName = ""; 409 mAppUid = uid; 410 mLastMetricLoggedTime = System.currentTimeMillis(); 411 } 412 413 /** 414 * Writes to SchedulingStatsLog if this entity has been blocking the reboot for longer 415 * than the given threshold. If this entity has been previously written to 416 * SchedulingStatsLog, the threshold will be compared with the time since the previous 417 * metric was recorded. 418 */ logLongRebootBlockingIfNecessary(long thresholdMs)419 private void logLongRebootBlockingIfNecessary(long thresholdMs) { 420 final long now = System.currentTimeMillis(); 421 if ((now - mLastMetricLoggedTime) > thresholdMs) { 422 int rebootBlockReason = mapEntityTypeToRebootBlockReason(mType); 423 SchedulingStatsLog.write(LONG_REBOOT_BLOCKING_REPORTED, rebootBlockReason, 424 mComponentName, mAppUid); 425 Log.i(TAG, "LongRebootBlockingReported " 426 + " rebootBlockReason=" + rebootBlockReasonToString(rebootBlockReason) 427 + " componentName=" + mComponentName 428 + " appUid=" + mAppUid); 429 mLastMetricLoggedTime = now; 430 } 431 } 432 433 /** 434 * Returns the reboot block reason which should be logged to SchedulingStatsLog if a given 435 * EntityType is blocking the reboot for a long time. 436 */ mapEntityTypeToRebootBlockReason(@ntityType int type)437 private static int mapEntityTypeToRebootBlockReason(@EntityType int type) { 438 if (type == ENTITY_TYPE_COMPONENT) { 439 return LONG_REBOOT_BLOCKING_REPORTED__REBOOT_BLOCK_REASON__SYSTEM_COMPONENT; 440 } else { 441 return LONG_REBOOT_BLOCKING_REPORTED__REBOOT_BLOCK_REASON__APP_UID; 442 } 443 } 444 445 /** 446 * Maps a reboot block reason to a readable string for logging purposes. 447 */ rebootBlockReasonToString(int rebootBlockReason)448 private static String rebootBlockReasonToString(int rebootBlockReason) { 449 switch (rebootBlockReason) { 450 case LONG_REBOOT_BLOCKING_REPORTED__REBOOT_BLOCK_REASON__APP_UID: 451 return "APP_UID"; 452 case LONG_REBOOT_BLOCKING_REPORTED__REBOOT_BLOCK_REASON__SYSTEM_COMPONENT: 453 return "SYSTEM_COMPONENT"; 454 default: 455 return "UNKNOWN"; 456 } 457 } 458 } 459 } 460