Added option to dump end-to-end duration of first unlocked user.
This will be useful to measure the impact of using the User HAL to get
the initial user.
Test: atest CarServicesTest:com.android.internal.car.CarHelperServiceTest \
CarServiceUnitTest:com.android.car.user.CarUserServiceTest
Test: m android.car.testapi
Test: adb shell dumpsys car_service --first-user-metrics
Bug: 141388849
Bug: 146207078
Bug: 150222501
Change-Id: Iaad06d3cd2c72061650c27a14cba7f16c1d809bc
diff --git a/car-lib/src/android/car/ICar.aidl b/car-lib/src/android/car/ICar.aidl
index 0b0ae11..a62d8fa 100644
--- a/car-lib/src/android/car/ICar.aidl
+++ b/car-lib/src/android/car/ICar.aidl
@@ -19,13 +19,11 @@
/** @hide */
interface ICar {
// All oneway methods are called from system server and should be placed in top positions.
- // Do not change the order of oneway methods as system server make binder call based on this
- // order.
+ // Do not change the number of oneway methods as system server make binder call based on this
+ // order - if you change them, you need to change the constants on CarServiceHelperService.
/**
* IBinder is ICarServiceHelper but passed as IBinder due to aidl hidden.
- *
- * This should be the 1st method. Do not change the order.
*/
oneway void setCarServiceHelper(in IBinder helper) = 0;
@@ -36,29 +34,36 @@
* @param timestampMs - when the event happened
* @param fromUserId - user id of previous user when type is SWITCHING (or UserHandle.USER_NULL)
* @param toUserId - user id of new user.
- *
- * This should be the 2nd method. Do not change the order.
*/
oneway void onUserLifecycleEvent(int eventType, long timestampMs, int fromUserId,
int toUserId) = 1;
/**
+ * Notify when first user was unlocked, for metrics (and lifecycle) purposes.
+ *
+ * @param userId - id of first non-system user locked
+ * @param timestampMs - when the user was unlocked
+ * @param duration - how long it took to unlock (from SystemServer start)
+ */
+ oneway void onFirstUserUnlocked(int userId, long timestampMs, long duration) = 2;
+
+
+ // TODO(b/145689885): 2 method below are deprecated (onUserLifecycleEvent covers then) so
+ // they're have higher codes to make it easier to add other
+
+ /**
* Notify lock / unlock of user id to car service.
* unlocked: 1 if unlocked 0 otherwise.
- *
- * This should be the 3rd method. Do not change the order.
*/
- oneway void setUserLockStatus(in int userId, in int unlocked) = 2;
+ oneway void setUserLockStatus(in int userId, in int unlocked) = 9;
/**
* Notify of user switching. This is called only for foreground users when the user is starting
* to boot.
*
* @param userId - user id of new user.
- *
- * This should be the 4th method. Do not change the order.
*/
- oneway void onSwitchUser(in int userId) = 3;
+ oneway void onSwitchUser(in int userId) = 10;
// Methods below start on 11 to make it easier to add more oneway methods above
diff --git a/car-test-lib/src/android/car/testapi/FakeCar.java b/car-test-lib/src/android/car/testapi/FakeCar.java
index 932ac76..b6f22fb 100644
--- a/car-test-lib/src/android/car/testapi/FakeCar.java
+++ b/car-test-lib/src/android/car/testapi/FakeCar.java
@@ -167,6 +167,11 @@
}
@Override
+ public void onFirstUserUnlocked(int userId, long timestampMs, long duration) {
+ // Nothing to do yet.
+ }
+
+ @Override
public void setUserLockStatus(int userHandle, int unlocked) throws RemoteException {
// Nothing to do yet.
}
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index a68e171..0ff4bc0 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -366,6 +366,11 @@
}
@Override
+ public void onFirstUserUnlocked(int userId, long timestampMs, long duration) {
+ mUserMetrics.logFirstUnlockedUser(userId, timestampMs, duration);
+ }
+
+ @Override
public boolean isFeatureEnabled(String featureName) {
return mFeatureController.isFeatureEnabled(featureName);
}
@@ -670,6 +675,8 @@
return;
} else if ("--user-metrics".equals(args[0])) {
mUserMetrics.dump(writer);
+ } else if ("--first-user-metrics".equals(args[0])) {
+ mUserMetrics.dumpFirstUserUnlockDuration(writer);
} else if ("--help".equals(args[0])) {
showDumpHelp(writer);
} else if (Build.IS_USERDEBUG || Build.IS_ENG) {
@@ -711,6 +718,9 @@
writer.println("\t where HAL is just the class name (like UserHalService)");
writer.println("--user-metrics");
writer.println("\t dumps user switching and stopping metrics ");
+ writer.println("--first-user-metrics");
+ writer.println("\t dumps how long it took to unlock first user since Android started\n");
+ writer.println("\t (or -1 if not unlocked)");
writer.println("-h");
writer.println("\t shows commands usage (NOTE: commands are not available on USER builds");
writer.println("[ANYTHING ELSE]");
diff --git a/service/src/com/android/car/user/UserMetrics.java b/service/src/com/android/car/user/UserMetrics.java
index dad71fd..6c94f28 100644
--- a/service/src/com/android/car/user/UserMetrics.java
+++ b/service/src/com/android/car/user/UserMetrics.java
@@ -31,6 +31,7 @@
import android.util.LocalLog;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseLongArray;
import android.util.TimeUtils;
import com.android.internal.annotations.GuardedBy;
@@ -78,6 +79,9 @@
@GuardedBy("mLock")
private final LocalLog mUserStoppedLogs = new LocalLog(LOG_SIZE);
+ @GuardedBy("mLock")
+ private final SparseLongArray mFirstUserUnlockDuration = new SparseLongArray(1);
+
/**
* Logs a user lifecycle event.
*/
@@ -109,6 +113,16 @@
}
}
+ /**
+ * Logs when the first user was unlocked.
+ */
+ public void logFirstUnlockedUser(int userId, long timestampMs, long duration) {
+ synchronized (mLock) {
+ mFirstUserUnlockDuration.put(userId, duration);
+ onUserUnlockedEventLocked(timestampMs, userId);
+ }
+ }
+
private void onUserStartingEventLocked(long timestampMs, @UserIdInt int userId) {
if (mUserStartingMetrics == null) {
mUserStartingMetrics = new SparseArray<>(INITIAL_CAPACITY);
@@ -209,6 +223,14 @@
pw.println("* User Metrics *");
synchronized (mLock) {
+ if (mFirstUserUnlockDuration.size() == 0) {
+ pw.println("First user not unlocked yet");
+ } else {
+ pw.printf("First user (%d) unlocked in ", mFirstUserUnlockDuration.keyAt(0));
+ TimeUtils.formatDuration(mFirstUserUnlockDuration.valueAt(0), pw);
+ pw.println();
+ }
+
dump(pw, "starting", mUserStartingMetrics);
dump(pw, "stopping", mUserStoppingMetrics);
@@ -220,6 +242,19 @@
}
}
+ /**
+ * Dumps only how long it took to unlock the first user (or {@code -1} if not available).
+ */
+ public void dumpFirstUserUnlockDuration(@NonNull PrintWriter pw) {
+ synchronized (mLock) {
+ if (mFirstUserUnlockDuration.size() == 0) {
+ pw.println(-1);
+ return;
+ }
+ pw.println(mFirstUserUnlockDuration.valueAt(0));
+ }
+ }
+
private void dump(@NonNull PrintWriter pw, @NonNull String message,
@NonNull SparseArray<? extends BaseUserMetric> metrics) {
String indent = " ";
diff --git a/tests/carservice_unit_test/src/android/car/CarTest.java b/tests/carservice_unit_test/src/android/car/CarTest.java
index 1da65ff..ddb18c7 100644
--- a/tests/carservice_unit_test/src/android/car/CarTest.java
+++ b/tests/carservice_unit_test/src/android/car/CarTest.java
@@ -78,6 +78,10 @@
}
@Override
+ public void onFirstUserUnlocked(int userId, long timestampMs, long duration) {
+ }
+
+ @Override
public void setUserLockStatus(int userHandle, int unlocked) {
}