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 try (FileOutputStream stream = rebootStatsFile.startWrite()) { 342 XmlWriter writer = new XmlWriter(new PrintWriter(stream)); 343 XmlWriter.write(writer, rebootStats); 344 writer.close(); 345 rebootStatsFile.finishWrite(stream); 346 } catch (Exception e) { 347 Log.e(TAG, "Could not write reboot readiness stats: " + e); 348 } 349 } 350 dump(PrintWriter pw)351 void dump(PrintWriter pw) { 352 synchronized (mLock) { 353 if (mBlockingComponents.size() > 0) { 354 writeBlockingSubsystems(pw); 355 } 356 357 if (mBlockingApps.size() > 0) { 358 writeBlockingUids(pw); 359 } 360 361 if (mShouldDumpMetrics) { 362 pw.println("Previous reboot readiness checks:"); 363 pw.println(" Start timestamp: " + mStartTime); 364 pw.println(" Ready timestamp: " + mReadyTime); 365 pw.println(" Times blocked by interactivity " + mTimesBlockedByInteractivity); 366 pw.println(" Times blocked by subsystems " + mTimesBlockedBySubsystems); 367 pw.println(" Times blocked by app activity " + mTimesBlockedByAppActivity); 368 pw.println(" Time until next interaction " + mTimeUntilNextInteractionMs); 369 } 370 } 371 } 372 373 374 /** 375 * Class for tracking system components or app uids which are blocking the reboot. Handles 376 * the tracking of how long an entity has blocked the reboot for, and handles the logging 377 * of LongRebootBlockingReported events to SchedulingStatsLog. 378 */ 379 private static final class BlockingEntityRecord { 380 381 private final int mType; 382 private String mComponentName; 383 private int mAppUid; 384 @CurrentTimeMillisLong private long mLastMetricLoggedTime; 385 386 @Retention(RetentionPolicy.SOURCE) 387 @IntDef({ 388 ENTITY_TYPE_COMPONENT, 389 ENTITY_TYPE_APP, 390 }) 391 private @interface EntityType {} 392 393 private static final int ENTITY_TYPE_COMPONENT = 1; 394 private static final int ENTITY_TYPE_APP = 2; 395 BlockingEntityRecord(String name)396 private BlockingEntityRecord(String name) { 397 mType = ENTITY_TYPE_COMPONENT; 398 mComponentName = name; 399 mLastMetricLoggedTime = System.currentTimeMillis(); 400 } 401 BlockingEntityRecord(int uid)402 private BlockingEntityRecord(int uid) { 403 mType = ENTITY_TYPE_APP; 404 mComponentName = ""; 405 mAppUid = uid; 406 mLastMetricLoggedTime = System.currentTimeMillis(); 407 } 408 409 /** 410 * Writes to SchedulingStatsLog if this entity has been blocking the reboot for longer 411 * than the given threshold. If this entity has been previously written to 412 * SchedulingStatsLog, the threshold will be compared with the time since the previous 413 * metric was recorded. 414 */ logLongRebootBlockingIfNecessary(long thresholdMs)415 private void logLongRebootBlockingIfNecessary(long thresholdMs) { 416 final long now = System.currentTimeMillis(); 417 if ((now - mLastMetricLoggedTime) > thresholdMs) { 418 int rebootBlockReason = mapEntityTypeToRebootBlockReason(mType); 419 SchedulingStatsLog.write(LONG_REBOOT_BLOCKING_REPORTED, rebootBlockReason, 420 mComponentName, mAppUid); 421 Log.i(TAG, "LongRebootBlockingReported " 422 + " rebootBlockReason=" + rebootBlockReasonToString(rebootBlockReason) 423 + " componentName=" + mComponentName 424 + " appUid=" + mAppUid); 425 mLastMetricLoggedTime = now; 426 } 427 } 428 429 /** 430 * Returns the reboot block reason which should be logged to SchedulingStatsLog if a given 431 * EntityType is blocking the reboot for a long time. 432 */ mapEntityTypeToRebootBlockReason(@ntityType int type)433 private static int mapEntityTypeToRebootBlockReason(@EntityType int type) { 434 if (type == ENTITY_TYPE_COMPONENT) { 435 return LONG_REBOOT_BLOCKING_REPORTED__REBOOT_BLOCK_REASON__SYSTEM_COMPONENT; 436 } else { 437 return LONG_REBOOT_BLOCKING_REPORTED__REBOOT_BLOCK_REASON__APP_UID; 438 } 439 } 440 441 /** 442 * Maps a reboot block reason to a readable string for logging purposes. 443 */ rebootBlockReasonToString(int rebootBlockReason)444 private static String rebootBlockReasonToString(int rebootBlockReason) { 445 switch (rebootBlockReason) { 446 case LONG_REBOOT_BLOCKING_REPORTED__REBOOT_BLOCK_REASON__APP_UID: 447 return "APP_UID"; 448 case LONG_REBOOT_BLOCKING_REPORTED__REBOOT_BLOCK_REASON__SYSTEM_COMPONENT: 449 return "SYSTEM_COMPONENT"; 450 default: 451 return "UNKNOWN"; 452 } 453 } 454 } 455 } 456