Implement LongRebootBlockingReported metric
Adds a new BlockingEntityRecord class which tracks a
component or app uid which is blocking the reboot. If a
given component/app block the reboot for a time greater than
a given threshold (default is one hour), this will be logged
to statsd with a LongRebootBlockingReported atom. This atom
may be logged multiple times if the entity continues to
block the reboot for multiple thresholds. The threshold
is configurable via DeviceConfig.
Maps of blocking components/apps are stored in RebootReadinessLogger. If
a previously blocking app/component is no longer blocking the
reboot, it will be pruned from the map.
Test: Manual. Configure DeviceConfig and test with a blocking
app and component and a threshold of 10 seconds. Note that
the correct data is written to logcat when the reboot has been
blocked for 10 seconds.
Bug: 183587376
Change-Id: Ic4769968d3dfe5a2e7cf2e49632d6551bcd98404
diff --git a/service/java/com/android/server/scheduling/RebootReadinessLogger.java b/service/java/com/android/server/scheduling/RebootReadinessLogger.java
index 74c366a..66cf210 100644
--- a/service/java/com/android/server/scheduling/RebootReadinessLogger.java
+++ b/service/java/com/android/server/scheduling/RebootReadinessLogger.java
@@ -16,11 +16,18 @@
package com.android.server.scheduling;
+import static com.android.server.scheduling.SchedulingStatsLog.LONG_REBOOT_BLOCKING_REPORTED;
+import static com.android.server.scheduling.SchedulingStatsLog.LONG_REBOOT_BLOCKING_REPORTED__REBOOT_BLOCK_REASON__APP_UID;
+import static com.android.server.scheduling.SchedulingStatsLog.LONG_REBOOT_BLOCKING_REPORTED__REBOOT_BLOCK_REASON__SYSTEM_COMPONENT;
+
import android.annotation.CurrentTimeMillisLong;
+import android.annotation.IntDef;
import android.content.ApexEnvironment;
import android.os.SystemClock;
+import android.util.ArrayMap;
import android.util.AtomicFile;
import android.util.Log;
+import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
@@ -28,6 +35,10 @@
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
/**
@@ -43,6 +54,12 @@
private final Object mLock = new Object();
@GuardedBy("mLock")
+ private final SparseArray<BlockingEntityRecord> mBlockingApps = new SparseArray<>();
+
+ @GuardedBy("mLock")
+ private final ArrayMap<String, BlockingEntityRecord> mBlockingComponents = new ArrayMap<>();
+
+ @GuardedBy("mLock")
private long mStartTime;
@GuardedBy("mLock")
@@ -101,7 +118,7 @@
rebootStats.setTimesBlockedBySubsystems(timesBlockedBySubsystems);
rebootStats.setTimesBlockedByAppActivity(timesBlockedByAppActivity);
try (
- FileOutputStream stream = rebootStatsFile.startWrite();
+ FileOutputStream stream = rebootStatsFile.startWrite()
) {
XmlWriter writer = new XmlWriter(new PrintWriter(stream));
XmlWriter.write(writer, rebootStats);
@@ -165,6 +182,71 @@
}
}
+ /**
+ * Tracks any components which are currently blocking the reboot. If any of the components have
+ * been blocking the reboot for longer than the given threshold, this information will be logged
+ * to SchedulingStatsLog. Any components which previously blocked the reboot but are currently
+ * not blocking the reboot will be pruned from the set of tracked components.
+ *
+ * @param blockingComponentNames list of component names which are blocking the reboot.
+ * @param thresholdMs the time a component may block the reboot for before being logged.
+ */
+ void maybeLogLongBlockingComponents(List<String> blockingComponentNames, long thresholdMs) {
+ synchronized (mLock) {
+ for (String component : blockingComponentNames) {
+ BlockingEntityRecord record = mBlockingComponents.get(component);
+ if (record == null) {
+ record = new BlockingEntityRecord(component);
+ mBlockingComponents.put(component, record);
+ }
+ record.logLongRebootBlockingIfNecessary(thresholdMs);
+ }
+
+ for (String existingRecordName : mBlockingComponents.keySet()) {
+ if (!blockingComponentNames.contains(existingRecordName)) {
+ mBlockingComponents.remove(existingRecordName);
+ }
+ }
+ }
+ }
+
+ /**
+ * Tracks any app uids which are currently blocking the reboot. If any of the apps have been
+ * blocking the reboot for longer than the given threshold, this information will be logged
+ * to SchedulingStatsLog. Any apps which previously blocked the reboot but are currently
+ * not blocking the reboot will be pruned from the set of tracked uids.
+ *
+ * @param blockingAppUids list of app uids which are blocking the reboot.
+ * @param thresholdMs the time an app may block the reboot for before being logged.
+ *
+ * TODO(b/184165442): Use IntArray instead.
+ */
+ void maybeLogLongBlockingApps(List<Integer> blockingAppUids, long thresholdMs) {
+ synchronized (mLock) {
+ for (Integer uid : blockingAppUids) {
+ BlockingEntityRecord record = mBlockingApps.get(uid);
+ if (record == null) {
+ record = new BlockingEntityRecord(uid);
+ mBlockingApps.put(uid, record);
+ }
+ record.logLongRebootBlockingIfNecessary(thresholdMs);
+ }
+
+ List<Integer> uidsToPrune = new ArrayList<>();
+ for (int i = 0; i < mBlockingApps.size(); i++) {
+ int uid = mBlockingApps.keyAt(i);
+ if (!blockingAppUids.contains(uid)) {
+ uidsToPrune.add(uid);
+ }
+ }
+
+ for (Integer uid : uidsToPrune) {
+ mBlockingApps.remove(uid);
+ }
+ }
+ }
+
+
private static AtomicFile getRebootStatsFile() {
File deDir = ApexEnvironment.getApexEnvironment(MODULE_NAME).getDeviceProtectedDataDir();
File file = new File(deDir, REBOOT_STATS_FILE);
@@ -187,4 +269,86 @@
}
}
}
+
+
+ /**
+ * Class for tracking system components or app uids which are blocking the reboot. Handles
+ * the tracking of how long an entity has blocked the reboot for, and handles the logging
+ * of LongRebootBlockingReported events to SchedulingStatsLog.
+ */
+ private static final class BlockingEntityRecord {
+
+ private final int mType;
+ private String mComponentName;
+ private int mAppUid;
+ @CurrentTimeMillisLong private long mLastMetricLoggedTime;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ ENTITY_TYPE_COMPONENT,
+ ENTITY_TYPE_APP,
+ })
+ private @interface EntityType {}
+
+ private static final int ENTITY_TYPE_COMPONENT = 1;
+ private static final int ENTITY_TYPE_APP = 2;
+
+ private BlockingEntityRecord(String name) {
+ mType = ENTITY_TYPE_COMPONENT;
+ mComponentName = name;
+ mLastMetricLoggedTime = System.currentTimeMillis();
+ }
+
+ private BlockingEntityRecord(int uid) {
+ mType = ENTITY_TYPE_APP;
+ mAppUid = uid;
+ mLastMetricLoggedTime = System.currentTimeMillis();
+ }
+
+ /**
+ * Writes to SchedulingStatsLog if this entity has been blocking the reboot for longer
+ * than the given threshold. If this entity has been previously written to
+ * SchedulingStatsLog, the threshold will be compared with the time since the previous
+ * metric was recorded.
+ */
+ private void logLongRebootBlockingIfNecessary(long thresholdMs) {
+ final long now = System.currentTimeMillis();
+ if ((now - mLastMetricLoggedTime) > thresholdMs) {
+ int rebootBlockReason = mapEntityTypeToRebootBlockReason(mType);
+ SchedulingStatsLog.write(LONG_REBOOT_BLOCKING_REPORTED, rebootBlockReason,
+ mComponentName, mAppUid);
+ Log.i(TAG, "LongRebootBlockingReported "
+ + " rebootBlockReason=" + rebootBlockReasonToString(rebootBlockReason)
+ + " componentName=" + mComponentName
+ + " appUid=" + mAppUid);
+ mLastMetricLoggedTime = now;
+ }
+ }
+
+ /**
+ * Returns the reboot block reason which should be logged to SchedulingStatsLog if a given
+ * EntityType is blocking the reboot for a long time.
+ */
+ private static int mapEntityTypeToRebootBlockReason(@EntityType int type) {
+ if (type == ENTITY_TYPE_COMPONENT) {
+ return LONG_REBOOT_BLOCKING_REPORTED__REBOOT_BLOCK_REASON__SYSTEM_COMPONENT;
+ } else {
+ return LONG_REBOOT_BLOCKING_REPORTED__REBOOT_BLOCK_REASON__APP_UID;
+ }
+ }
+
+ /**
+ * Maps a reboot block reason to a readable string for logging purposes.
+ */
+ private static String rebootBlockReasonToString(int rebootBlockReason) {
+ switch (rebootBlockReason) {
+ case LONG_REBOOT_BLOCKING_REPORTED__REBOOT_BLOCK_REASON__APP_UID:
+ return "APP_UID";
+ case LONG_REBOOT_BLOCKING_REPORTED__REBOOT_BLOCK_REASON__SYSTEM_COMPONENT:
+ return "SYSTEM_COMPONENT";
+ default:
+ return "UNKNOWN";
+ }
+ }
+ }
}
diff --git a/service/java/com/android/server/scheduling/RebootReadinessManagerService.java b/service/java/com/android/server/scheduling/RebootReadinessManagerService.java
index afa5372..f464145 100644
--- a/service/java/com/android/server/scheduling/RebootReadinessManagerService.java
+++ b/service/java/com/android/server/scheduling/RebootReadinessManagerService.java
@@ -96,6 +96,8 @@
private static final String PROPERTY_DISABLE_APP_ACTIVITY_CHECK = "disable_app_activity_check";
private static final String PROPERTY_DISABLE_SUBSYSTEMS_CHECK = "disable_subsystems_check";
private static final String PROPERTY_ALARM_CLOCK_THRESHOLD_MS = "alarm_clock_threshold_ms";
+ private static final String PROPERTY_LOGGING_BLOCKING_ENTITY_THRESHOLD_MS =
+ "logging_blocking_entity_threshold_ms";
private static final long DEFAULT_POLLING_INTERVAL_WHILE_IDLE_MS = TimeUnit.MINUTES.toMillis(1);
@@ -103,6 +105,8 @@
TimeUnit.MINUTES.toMillis(5);
private static final long DEFAULT_INTERACTIVITY_THRESHOLD_MS = TimeUnit.MINUTES.toMillis(30);
private static final long DEFAULT_ALARM_CLOCK_THRESHOLD_MS = TimeUnit.MINUTES.toMillis(10);
+ private static final long DEFAULT_LOGGING_BLOCKING_ENTITY_THRESHOLD_MS =
+ TimeUnit.HOURS.toMillis(1);
@GuardedBy("mLock")
private long mActivePollingIntervalMs = DEFAULT_POLLING_INTERVAL_WHILE_ACTIVE_MS;
@@ -128,6 +132,9 @@
@GuardedBy("mLock")
private long mAlarmClockThresholdMs = DEFAULT_ALARM_CLOCK_THRESHOLD_MS;
+ @GuardedBy("mLock")
+ private long mLoggingBlockingEntityThresholdMs = DEFAULT_LOGGING_BLOCKING_ENTITY_THRESHOLD_MS;
+
// A mapping of uid to package name for uids which have called markRebootPending. Reboot
// readiness state changed broadcasts will only be sent to the values in this map.
@GuardedBy("mLock")
@@ -366,6 +373,7 @@
}
final List<IRequestRebootReadinessStatusListener> blockingCallbacks = new ArrayList<>();
+ final List<String> blockingCallbackNames = new ArrayList<>();
int i = mCallbacks.beginBroadcast();
CountDownLatch latch = new CountDownLatch(i);
while (i > 0) {
@@ -381,6 +389,7 @@
if (!isReadyToReboot && (!mDisableSubsystemsCheck
|| name.startsWith(TEST_CALLBACK_PREFIX))) {
blockingCallbacks.add(callback);
+ blockingCallbackNames.add(name);
}
latch.countDown();
}
@@ -396,6 +405,8 @@
} catch (InterruptedException ignore) {
}
mCallbacks.finishBroadcast();
+ mRebootReadinessLogger.maybeLogLongBlockingComponents(blockingCallbackNames,
+ mLoggingBlockingEntityThresholdMs);
return blockingCallbacks.size() == 0;
}
@@ -414,13 +425,16 @@
if (mActivityManager != null) {
final List<RunningServiceInfo> serviceInfos =
mActivityManager.getRunningServices(Integer.MAX_VALUE);
+ List<Integer> blockingUids = new ArrayList<>();
for (int i = 0; i < serviceInfos.size(); i++) {
RunningServiceInfo info = serviceInfos.get(i);
if (info.foreground) {
- return false;
+ blockingUids.add(info.uid);
}
}
- return true;
+ mRebootReadinessLogger.maybeLogLongBlockingApps(blockingUids,
+ mLoggingBlockingEntityThresholdMs);
+ return blockingUids.size() == 0;
}
return false;
}
@@ -485,6 +499,10 @@
mAlarmClockThresholdMs = DeviceConfig.getLong(
DeviceConfig.NAMESPACE_REBOOT_READINESS,
PROPERTY_ALARM_CLOCK_THRESHOLD_MS, DEFAULT_ALARM_CLOCK_THRESHOLD_MS);
+ mLoggingBlockingEntityThresholdMs = DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_REBOOT_READINESS,
+ PROPERTY_LOGGING_BLOCKING_ENTITY_THRESHOLD_MS,
+ DEFAULT_LOGGING_BLOCKING_ENTITY_THRESHOLD_MS);
}
}