• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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