1 /* 2 * Copyright (C) 2020 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.car.user; 18 19 import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STARTING; 20 import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STOPPED; 21 import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STOPPING; 22 import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING; 23 import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED; 24 import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKING; 25 import static android.car.user.CarUserManager.lifecycleEventTypeToString; 26 27 import android.annotation.NonNull; 28 import android.annotation.Nullable; 29 import android.annotation.UserIdInt; 30 import android.car.user.CarUserManager.UserLifecycleEventType; 31 import android.util.LocalLog; 32 import android.util.Slog; 33 import android.util.SparseArray; 34 import android.util.SparseLongArray; 35 import android.util.TimeUtils; 36 37 import com.android.internal.annotations.GuardedBy; 38 import com.android.internal.annotations.VisibleForTesting; 39 40 import java.io.PrintWriter; 41 import java.io.StringWriter; 42 43 /** 44 * Metrics for user switches. 45 * 46 * <p>It stores 2 types of metrics: 47 * 48 * <ol> 49 * <li>Time to start a user (from start to unlock) 50 * <li>Time to stop a user (from stop to shutdown) 51 * </ol> 52 * 53 * <p>It keeps track of the users being started and stopped, then logs the last 54 * {{@link #INITIAL_CAPACITY}} occurrences of each when the operation finished (so it can be dumped 55 * later). 56 */ 57 final class UserMetrics { 58 59 private static final String TAG = UserMetrics.class.getSimpleName(); 60 61 /** 62 * Initial capacity for the current operations. 63 */ 64 // Typically there are at most 2 users (system and 1st full), although it could be higher on 65 // garage mode 66 private static final int INITIAL_CAPACITY = 2; 67 68 // TODO(b/150413515): read from resources 69 private static final int LOG_SIZE = 10; 70 71 private final Object mLock = new Object(); 72 73 @GuardedBy("mLock") 74 private SparseArray<UserStartingMetric> mUserStartingMetrics; 75 @GuardedBy("mLock") 76 private SparseArray<UserStoppingMetric> mUserStoppingMetrics; 77 78 @GuardedBy("mLock") 79 private final LocalLog mUserStartedLogs = new LocalLog(LOG_SIZE); 80 @GuardedBy("mLock") 81 private final LocalLog mUserStoppedLogs = new LocalLog(LOG_SIZE); 82 83 @GuardedBy("mLock") 84 private final SparseLongArray mFirstUserUnlockDuration = new SparseLongArray(1); 85 86 @GuardedBy("mLock") 87 private int mHalResponseTime; 88 89 /** 90 * Logs a user lifecycle event. 91 */ onEvent(@serLifecycleEventType int eventType, long timestampMs, @UserIdInt int fromUserId, @UserIdInt int toUserId)92 public void onEvent(@UserLifecycleEventType int eventType, long timestampMs, 93 @UserIdInt int fromUserId, @UserIdInt int toUserId) { 94 synchronized (mLock) { 95 switch(eventType) { 96 case USER_LIFECYCLE_EVENT_TYPE_STARTING: 97 onUserStartingEventLocked(timestampMs, toUserId); 98 return; 99 case USER_LIFECYCLE_EVENT_TYPE_SWITCHING: 100 onUserSwitchingEventLocked(timestampMs, fromUserId, toUserId); 101 return; 102 case USER_LIFECYCLE_EVENT_TYPE_UNLOCKING: 103 onUserUnlockingEventLocked(timestampMs, toUserId); 104 return; 105 case USER_LIFECYCLE_EVENT_TYPE_UNLOCKED: 106 onUserUnlockedEventLocked(timestampMs, toUserId); 107 return; 108 case USER_LIFECYCLE_EVENT_TYPE_STOPPING: 109 onUserStoppingEventLocked(timestampMs, toUserId); 110 return; 111 case USER_LIFECYCLE_EVENT_TYPE_STOPPED: 112 onUserStoppedEventLocked(timestampMs, toUserId); 113 return; 114 default: 115 Slog.w(TAG, "Invalid event: " + lifecycleEventTypeToString(eventType)); 116 } 117 } 118 } 119 120 /** 121 * Logs when the first user was unlocked. 122 */ logFirstUnlockedUser(int userId, long timestampMs, long duration, int halResponseTime)123 public void logFirstUnlockedUser(int userId, long timestampMs, long duration, 124 int halResponseTime) { 125 synchronized (mLock) { 126 mHalResponseTime = halResponseTime; 127 mFirstUserUnlockDuration.put(userId, duration); 128 } 129 } 130 131 @VisibleForTesting getUserStartMetrics()132 SparseArray<UserStartingMetric> getUserStartMetrics() { 133 synchronized (mLock) { 134 return mUserStartingMetrics; 135 } 136 } 137 138 @VisibleForTesting getUserStopMetrics()139 SparseArray<UserStoppingMetric> getUserStopMetrics() { 140 synchronized (mLock) { 141 return mUserStoppingMetrics; 142 } 143 } 144 onUserStartingEventLocked(long timestampMs, @UserIdInt int userId)145 private void onUserStartingEventLocked(long timestampMs, @UserIdInt int userId) { 146 if (mUserStartingMetrics == null) { 147 mUserStartingMetrics = new SparseArray<>(INITIAL_CAPACITY); 148 } 149 150 UserStartingMetric existingMetrics = mUserStartingMetrics.get(userId); 151 if (existingMetrics != null) { 152 Slog.w(TAG, "user re-started: " + existingMetrics); 153 finishUserStartingLocked(existingMetrics, /* removeMetric= */ false); 154 } 155 156 mUserStartingMetrics.put(userId, new UserStartingMetric(userId, timestampMs)); 157 } 158 onUserSwitchingEventLocked(long timestampMs, @UserIdInt int fromUserId, @UserIdInt int toUserId)159 private void onUserSwitchingEventLocked(long timestampMs, @UserIdInt int fromUserId, 160 @UserIdInt int toUserId) { 161 UserStartingMetric metrics = getExistingMetricsLocked(mUserStartingMetrics, toUserId); 162 if (metrics == null) return; 163 164 metrics.switchFromUserId = fromUserId; 165 metrics.switchTime = timestampMs; 166 } 167 onUserUnlockingEventLocked(long timestampMs, @UserIdInt int userId)168 private void onUserUnlockingEventLocked(long timestampMs, @UserIdInt int userId) { 169 UserStartingMetric metrics = getExistingMetricsLocked(mUserStartingMetrics, userId); 170 if (metrics == null) return; 171 172 metrics.unlockingTime = timestampMs; 173 } 174 onUserUnlockedEventLocked(long timestampMs, @UserIdInt int userId)175 private void onUserUnlockedEventLocked(long timestampMs, @UserIdInt int userId) { 176 UserStartingMetric metrics = getExistingMetricsLocked(mUserStartingMetrics, userId); 177 if (metrics == null) return; 178 179 metrics.unlockedTime = timestampMs; 180 181 finishUserStartingLocked(metrics, /* removeMetric= */ true); 182 } 183 onUserStoppingEventLocked(long timestampMs, @UserIdInt int userId)184 private void onUserStoppingEventLocked(long timestampMs, @UserIdInt int userId) { 185 if (mUserStoppingMetrics == null) { 186 mUserStoppingMetrics = new SparseArray<>(INITIAL_CAPACITY); 187 } 188 UserStoppingMetric existingMetrics = mUserStoppingMetrics.get(userId); 189 if (existingMetrics != null) { 190 Slog.w(TAG, "user re-stopped: " + existingMetrics); 191 finishUserStoppingLocked(existingMetrics, /* removeMetric= */ false); 192 } 193 mUserStoppingMetrics.put(userId, new UserStoppingMetric(userId, timestampMs)); 194 } 195 onUserStoppedEventLocked(long timestampMs, @UserIdInt int userId)196 private void onUserStoppedEventLocked(long timestampMs, @UserIdInt int userId) { 197 UserStoppingMetric metrics = getExistingMetricsLocked(mUserStoppingMetrics, userId); 198 if (metrics == null) return; 199 200 metrics.shutdownTime = timestampMs; 201 finishUserStoppingLocked(metrics, /* removeMetric= */ true); 202 } 203 204 @Nullable getExistingMetricsLocked( @onNull SparseArray<? extends BaseUserMetric> metrics, @UserIdInt int userId)205 private <T extends BaseUserMetric> T getExistingMetricsLocked( 206 @NonNull SparseArray<? extends BaseUserMetric> metrics, @UserIdInt int userId) { 207 if (metrics == null) { 208 Slog.w(TAG, "getExistingMetricsLocked() should not pass null metrics, except on tests"); 209 return null; 210 } 211 @SuppressWarnings("unchecked") 212 T metric = (T) metrics.get(userId); 213 if (metric == null) { 214 String name = metrics == mUserStartingMetrics ? "starting" : "stopping"; 215 Slog.w(TAG, "no " + name + " metrics for user " + userId); 216 } 217 return metric; 218 } 219 removeExistingMetricsLogged(@onNull SparseArray<? extends BaseUserMetric> metrics, @UserIdInt int userId)220 private void removeExistingMetricsLogged(@NonNull SparseArray<? extends BaseUserMetric> metrics, 221 @UserIdInt int userId) { 222 metrics.remove(userId); 223 if (metrics.size() != 0) return; 224 225 if (metrics == mUserStartingMetrics) { 226 mUserStartingMetrics = null; 227 } else { 228 mUserStoppingMetrics = null; 229 } 230 } 231 finishUserStartingLocked(@onNull UserStartingMetric metrics, boolean removeMetric)232 private void finishUserStartingLocked(@NonNull UserStartingMetric metrics, 233 boolean removeMetric) { 234 mUserStartedLogs.log(metrics.toString()); 235 if (removeMetric) { 236 removeExistingMetricsLogged(mUserStartingMetrics, metrics.userId); 237 } 238 } 239 finishUserStoppingLocked(@onNull UserStoppingMetric metrics, boolean removeMetric)240 private void finishUserStoppingLocked(@NonNull UserStoppingMetric metrics, 241 boolean removeMetric) { 242 mUserStoppedLogs.log(metrics.toString()); 243 if (removeMetric) { 244 removeExistingMetricsLogged(mUserStoppingMetrics, metrics.userId); 245 } 246 } 247 248 /** 249 * Dumps its contents. 250 */ dump(@onNull PrintWriter pw)251 public void dump(@NonNull PrintWriter pw) { 252 pw.println("* User Metrics *"); 253 synchronized (mLock) { 254 255 if (mFirstUserUnlockDuration.size() == 0) { 256 pw.println("First user not unlocked yet"); 257 } else { 258 pw.printf("First user (%d) unlocked in ", mFirstUserUnlockDuration.keyAt(0)); 259 TimeUtils.formatDuration(mFirstUserUnlockDuration.valueAt(0), pw); 260 pw.println(); 261 } 262 263 dump(pw, "starting", mUserStartingMetrics); 264 dump(pw, "stopping", mUserStoppingMetrics); 265 266 pw.printf("Last %d started users\n", LOG_SIZE); 267 mUserStartedLogs.dump(" ", pw); 268 269 pw.printf("Last %d stopped users\n", LOG_SIZE); 270 mUserStoppedLogs.dump(" ", pw); 271 272 pw.print("HAL response time: "); 273 if (mHalResponseTime == 0) { 274 pw.print("N/A"); 275 } else if (mHalResponseTime < 0) { 276 pw.print("not replied yet, sent at "); 277 TimeUtils.formatUptime(-mHalResponseTime); 278 } else { 279 TimeUtils.formatDuration(mHalResponseTime, pw); 280 } 281 pw.println(); 282 } 283 } 284 285 /** 286 * Dumps only how long it took to unlock the first user (or {@code -1} if not available). 287 */ dumpFirstUserUnlockDuration(@onNull PrintWriter pw)288 public void dumpFirstUserUnlockDuration(@NonNull PrintWriter pw) { 289 synchronized (mLock) { 290 if (mFirstUserUnlockDuration.size() == 0) { 291 pw.println(-1); 292 return; 293 } 294 pw.println(mFirstUserUnlockDuration.valueAt(0)); 295 } 296 } 297 dump(@onNull PrintWriter pw, @NonNull String message, @NonNull SparseArray<? extends BaseUserMetric> metrics)298 private void dump(@NonNull PrintWriter pw, @NonNull String message, 299 @NonNull SparseArray<? extends BaseUserMetric> metrics) { 300 String indent = " "; 301 if (metrics == null) { 302 pw.printf("%sno users %s\n", indent, message); 303 return; 304 } 305 int size = metrics.size(); 306 pw.printf("%d users %s\n", size, message); 307 for (int i = 0; i < size; i++) { 308 BaseUserMetric metric = metrics.valueAt(i); 309 pw.printf("%s%d: ", indent, i); 310 metric.dump(pw); 311 pw.println(); 312 } 313 } 314 315 private abstract class BaseUserMetric { 316 public final @UserIdInt int userId; 317 BaseUserMetric(@serIdInt int userId)318 protected BaseUserMetric(@UserIdInt int userId) { 319 this.userId = userId; 320 } 321 322 @Override toString()323 public String toString() { 324 StringWriter sw = new StringWriter(); 325 PrintWriter pw = new PrintWriter(sw); 326 dump(pw); 327 pw.flush(); 328 return sw.toString(); 329 } 330 dump(@onNull PrintWriter pw)331 abstract void dump(@NonNull PrintWriter pw); 332 } 333 334 @VisibleForTesting 335 final class UserStartingMetric extends BaseUserMetric { 336 public final long startTime; 337 public long switchTime; 338 public long unlockingTime; 339 public long unlockedTime; 340 public @UserIdInt int switchFromUserId; 341 UserStartingMetric(@serIdInt int userId, long startTime)342 UserStartingMetric(@UserIdInt int userId, long startTime) { 343 super(userId); 344 this.startTime = startTime; 345 } 346 347 @Override dump(@onNull PrintWriter pw)348 public void dump(@NonNull PrintWriter pw) { 349 pw.printf("user=%d start=", userId); 350 TimeUtils.dumpTime(pw, startTime); 351 352 if (switchTime > 0) { 353 long delta = switchTime - startTime; 354 pw.print(" switch"); 355 if (switchFromUserId != 0) { 356 pw.printf("(from %d)", switchFromUserId); 357 } 358 pw.print('='); 359 TimeUtils.formatDuration(delta, pw); 360 } 361 362 if (unlockingTime > 0) { 363 long delta = unlockingTime - startTime; 364 pw.print(" unlocking="); 365 TimeUtils.formatDuration(delta, pw); 366 } 367 if (unlockedTime > 0) { 368 long delta = unlockedTime - startTime; 369 pw.print(" unlocked="); 370 TimeUtils.formatDuration(delta, pw); 371 } 372 } 373 } 374 375 @VisibleForTesting 376 final class UserStoppingMetric extends BaseUserMetric { 377 public final long stopTime; 378 public long shutdownTime; 379 UserStoppingMetric(@serIdInt int userId, long stopTime)380 UserStoppingMetric(@UserIdInt int userId, long stopTime) { 381 super(userId); 382 this.stopTime = stopTime; 383 } 384 385 @Override dump(@onNull PrintWriter pw)386 public void dump(@NonNull PrintWriter pw) { 387 pw.printf("user=%d stop=", userId); 388 TimeUtils.dumpTime(pw, stopTime); 389 390 if (shutdownTime > 0) { 391 long delta = shutdownTime - stopTime; 392 pw.print(" shutdown="); 393 TimeUtils.formatDuration(delta, pw); 394 } 395 } 396 } 397 } 398