Log UserspaceRebootReported atom from system_server
UserspaceRebootLogger class is introduced to encapsulate (tiny) state
machine for logging of userspace reboot.
So far state is stored in a persistent property, but that can be changed
in the future.
Unit tests will be added in a follow-up CL.
Test: adb shell svc power reboot userspace
Bug: 148767783
Change-Id: Ib9e2c6cef7094ccac7862be249d619212f0013fd
(cherry picked from commit d5594e2f901ba7eced97b35da8cf0f0a3f6242bd)
diff --git a/Android.bp b/Android.bp
index df852bd..412099d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -372,6 +372,7 @@
"devicepolicyprotosnano",
"com.android.sysprop.apex",
+ "com.android.sysprop.init",
"PlatformProperties",
],
sdk_version: "core_platform",
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 8449923..dadbb04 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -378,7 +378,7 @@
240 [(module) = "framework"];
BootTimeEventUtcTime boot_time_event_utc_time_reported = 241;
BootTimeEventErrorCode boot_time_event_error_code_reported = 242 [(module) = "framework"];
- UserspaceRebootReported userspace_reboot_reported = 243;
+ UserspaceRebootReported userspace_reboot_reported = 243 [(module) = "framework"];
NotificationReported notification_reported = 244 [(module) = "framework"];
NotificationPanelReported notification_panel_reported = 245;
NotificationChannelModified notification_panel_modified = 246;
diff --git a/services/core/java/com/android/server/UserspaceRebootLogger.java b/services/core/java/com/android/server/UserspaceRebootLogger.java
new file mode 100644
index 0000000..74f113f
--- /dev/null
+++ b/services/core/java/com/android/server/UserspaceRebootLogger.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_SHUTDOWN_SEQUENCE_ABORTED;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERDATA_REMOUNT;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERSPACE_REBOOT_WATCHDOG_TRIGGERED;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__OUTCOME_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__SUCCESS;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__LOCKED;
+import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__UNLOCKED;
+
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.util.Slog;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Utility class to help abstract logging {@code UserspaceRebootReported} atom.
+ */
+public final class UserspaceRebootLogger {
+
+ private static final String TAG = "UserspaceRebootLogger";
+
+ private static final String USERSPACE_REBOOT_SHOULD_LOG_PROPERTY =
+ "persist.sys.userspace_reboot.log.should_log";
+ private static final String USERSPACE_REBOOT_LAST_STARTED_PROPERTY =
+ "sys.userspace_reboot.log.last_started";
+ private static final String USERSPACE_REBOOT_LAST_FINISHED_PROPERTY =
+ "sys.userspace_reboot.log.last_finished";
+ private static final String BOOT_REASON_PROPERTY = "sys.boot.reason";
+
+ private UserspaceRebootLogger() {}
+
+ /**
+ * Modifies internal state to note that {@code UserspaceRebootReported} atom needs to be
+ * logged on the next successful boot.
+ */
+ public static void noteUserspaceRebootWasRequested() {
+ SystemProperties.set(USERSPACE_REBOOT_SHOULD_LOG_PROPERTY, "1");
+ SystemProperties.set(USERSPACE_REBOOT_LAST_STARTED_PROPERTY,
+ String.valueOf(SystemClock.elapsedRealtime()));
+ }
+
+ /**
+ * Updates internal state on boot after successful userspace reboot.
+ *
+ * <p>Should be called right before framework sets {@code sys.boot_completed} property.
+ */
+ public static void noteUserspaceRebootSuccess() {
+ SystemProperties.set(USERSPACE_REBOOT_LAST_FINISHED_PROPERTY,
+ String.valueOf(SystemClock.elapsedRealtime()));
+ }
+
+ /**
+ * Returns {@code true} if {@code UserspaceRebootReported} atom should be logged.
+ */
+ public static boolean shouldLogUserspaceRebootEvent() {
+ return SystemProperties.getBoolean(USERSPACE_REBOOT_SHOULD_LOG_PROPERTY, false);
+ }
+
+ /**
+ * Asynchronously logs {@code UserspaceRebootReported} on the given {@code executor}.
+ *
+ * <p>Should be called in the end of {@link
+ * com.android.server.am.ActivityManagerService#finishBooting()} method, after framework have
+ * tried to proactivelly unlock storage of the primary user.
+ */
+ public static void logEventAsync(boolean userUnlocked, Executor executor) {
+ final int outcome = computeOutcome();
+ final long durationMillis;
+ if (outcome == USERSPACE_REBOOT_REPORTED__OUTCOME__SUCCESS) {
+ durationMillis = SystemProperties.getLong(USERSPACE_REBOOT_LAST_FINISHED_PROPERTY, 0)
+ - SystemProperties.getLong(USERSPACE_REBOOT_LAST_STARTED_PROPERTY, 0);
+ } else {
+ durationMillis = 0;
+ }
+ final int encryptionState =
+ userUnlocked
+ ? USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__UNLOCKED
+ : USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__LOCKED;
+ executor.execute(
+ () -> {
+ Slog.i(TAG, "Logging UserspaceRebootReported atom: { outcome: " + outcome
+ + " durationMillis: " + durationMillis + " encryptionState: "
+ + encryptionState + " }");
+ FrameworkStatsLog.write(FrameworkStatsLog.USERSPACE_REBOOT_REPORTED, outcome,
+ durationMillis, encryptionState);
+ SystemProperties.set(USERSPACE_REBOOT_SHOULD_LOG_PROPERTY, "");
+ });
+ }
+
+ private static int computeOutcome() {
+ if (SystemProperties.getLong(USERSPACE_REBOOT_LAST_STARTED_PROPERTY, -1) != -1) {
+ return USERSPACE_REBOOT_REPORTED__OUTCOME__SUCCESS;
+ }
+ String reason = SystemProperties.get(BOOT_REASON_PROPERTY, "");
+ if (reason.startsWith("reboot,")) {
+ reason = reason.substring("reboot".length());
+ }
+ switch (reason) {
+ case "userspace_failed,watchdog_fork":
+ // Since fork happens before shutdown sequence, attribute it to
+ // USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_SHUTDOWN_SEQUENCE_ABORTED.
+ case "userspace_failed,shutdown_aborted":
+ return USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_SHUTDOWN_SEQUENCE_ABORTED;
+ case "userspace_failed,init_user0_failed":
+ // init_user0 will fail if userdata wasn't remounted correctly, attribute to
+ // USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERDATA_REMOUNT.
+ case "mount_userdata_failed":
+ return USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERDATA_REMOUNT;
+ case "userspace_failed,watchdog_triggered":
+ return
+ USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERSPACE_REBOOT_WATCHDOG_TRIGGERED;
+ default:
+ return USERSPACE_REBOOT_REPORTED__OUTCOME__OUTCOME_UNKNOWN;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c01766f..764d261 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -278,6 +278,7 @@
import android.provider.DeviceConfig.Properties;
import android.provider.Settings;
import android.server.ServerProtoEnums;
+import android.sysprop.InitProperties;
import android.sysprop.VoldProperties;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@@ -347,6 +348,7 @@
import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
import com.android.server.ThreadPriorityBooster;
+import com.android.server.UserspaceRebootLogger;
import com.android.server.Watchdog;
import com.android.server.am.ActivityManagerServiceDumpProcessesProto.UidObserverRegistrationProto;
import com.android.server.appop.AppOpsService;
@@ -2281,6 +2283,20 @@
}
}
+ private void maybeLogUserspaceRebootEvent() {
+ if (!UserspaceRebootLogger.shouldLogUserspaceRebootEvent()) {
+ return;
+ }
+ final int userId = mUserController.getCurrentUserId();
+ if (userId != UserHandle.USER_SYSTEM) {
+ // Only log for user0.
+ return;
+ }
+ // TODO(b/148767783): should we check all profiles under user0?
+ UserspaceRebootLogger.logEventAsync(StorageManager.isUserKeyUnlocked(userId),
+ BackgroundThread.getExecutor());
+ }
+
/**
* Encapsulates global settings related to hidden API enforcement behaviour, including tracking
* the latest value via a content observer.
@@ -5327,6 +5343,12 @@
// Start looking for apps that are abusing wake locks.
Message nmsg = mHandler.obtainMessage(CHECK_EXCESSIVE_POWER_USE_MSG);
mHandler.sendMessageDelayed(nmsg, mConstants.POWER_CHECK_INTERVAL);
+ // Check if we are performing userspace reboot before setting sys.boot_completed to
+ // avoid race with init reseting sys.init.userspace_reboot.in_progress once sys
+ // .boot_completed is 1.
+ if (InitProperties.userspace_reboot_in_progress().orElse(false)) {
+ UserspaceRebootLogger.noteUserspaceRebootSuccess();
+ }
// Tell anyone interested that we are done booting!
SystemProperties.set("sys.boot_completed", "1");
@@ -5347,6 +5369,7 @@
}
}
});
+ maybeLogUserspaceRebootEvent();
mUserController.scheduleStartProfiles();
}
// UART is on if init's console service is running, send a warning notification.
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 002ab9c..e7b4c9c 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -25,6 +25,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.SynchronousUserSwitchObserver;
@@ -95,6 +96,7 @@
import com.android.server.ServiceThread;
import com.android.server.SystemService;
import com.android.server.UiThread;
+import com.android.server.UserspaceRebootLogger;
import com.android.server.Watchdog;
import com.android.server.am.BatteryStatsService;
import com.android.server.lights.LightsManager;
@@ -3153,7 +3155,10 @@
}
private void shutdownOrRebootInternal(final @HaltMode int haltMode, final boolean confirm,
- final String reason, boolean wait) {
+ @Nullable final String reason, boolean wait) {
+ if (PowerManager.REBOOT_USERSPACE.equals(reason)) {
+ UserspaceRebootLogger.noteUserspaceRebootWasRequested();
+ }
if (mHandler == null || !mSystemReady) {
if (RescueParty.isAttemptingFactoryReset()) {
// If we're stuck in a really low-level reboot loop, and a
@@ -5038,7 +5043,7 @@
* @param wait If true, this call waits for the reboot to complete and does not return.
*/
@Override // Binder call
- public void reboot(boolean confirm, String reason, boolean wait) {
+ public void reboot(boolean confirm, @Nullable String reason, boolean wait) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null);
if (PowerManager.REBOOT_RECOVERY.equals(reason)
|| PowerManager.REBOOT_RECOVERY_UPDATE.equals(reason)) {