blob: c059721eb2d3e5af1044e1ae0a2f6d2dbbed9266 [file] [log] [blame]
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +01001/*
2 * Copyright (C) 2018 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
17package com.android.internal.os;
18
19import android.annotation.NonNull;
Marcin Oczeretkoa674bc92018-09-13 18:20:21 +010020import android.annotation.Nullable;
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +010021import android.os.Handler;
22import android.os.Looper;
23import android.os.Message;
24import android.os.SystemClock;
25import android.util.SparseArray;
26
27import com.android.internal.annotations.GuardedBy;
28
29import java.util.ArrayList;
30import java.util.List;
31import java.util.concurrent.ConcurrentLinkedQueue;
32import java.util.concurrent.ThreadLocalRandom;
33
34/**
35 * Collects aggregated telemetry data about Looper message dispatching.
36 *
37 * @hide Only for use within the system server.
38 */
39public class LooperStats implements Looper.Observer {
Marcin Oczeretko94501712018-12-17 18:03:45 +000040 public static final String DEBUG_ENTRY_PREFIX = "__DEBUG_";
Marcin Oczeretkoa674bc92018-09-13 18:20:21 +010041 private static final int SESSION_POOL_SIZE = 50;
Olivier Gaillard36b80ca2019-02-11 11:41:39 +000042 private static final boolean DISABLED_SCREEN_STATE_TRACKING_VALUE = false;
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +010043
44 @GuardedBy("mLock")
Marcin Oczeretkoc80c81a2018-08-30 20:15:52 +010045 private final SparseArray<Entry> mEntries = new SparseArray<>(512);
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +010046 private final Object mLock = new Object();
47 private final Entry mOverflowEntry = new Entry("OVERFLOW");
48 private final Entry mHashCollisionEntry = new Entry("HASH_COLLISION");
49 private final ConcurrentLinkedQueue<DispatchSession> mSessionPool =
50 new ConcurrentLinkedQueue<>();
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +010051 private final int mEntriesSizeCap;
Marcin Oczeretko3680ae62018-08-23 16:33:34 +010052 private int mSamplingInterval;
Marcin Oczeretkoc80c81a2018-08-30 20:15:52 +010053 private CachedDeviceState.Readonly mDeviceState;
Marcin Oczeretko6a2e5242018-11-28 11:08:50 +000054 private CachedDeviceState.TimeInStateStopwatch mBatteryStopwatch;
Olivier Gaillard28109b52018-12-14 15:14:14 +000055 private long mStartCurrentTime = System.currentTimeMillis();
56 private long mStartElapsedTime = SystemClock.elapsedRealtime();
Marcin Oczeretkod32a51352018-11-20 18:11:37 +000057 private boolean mAddDebugEntries = false;
Olivier Gaillard36b80ca2019-02-11 11:41:39 +000058 private boolean mTrackScreenInteractive = false;
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +010059
60 public LooperStats(int samplingInterval, int entriesSizeCap) {
61 this.mSamplingInterval = samplingInterval;
62 this.mEntriesSizeCap = entriesSizeCap;
63 }
64
Marcin Oczeretkoc80c81a2018-08-30 20:15:52 +010065 public void setDeviceState(@NonNull CachedDeviceState.Readonly deviceState) {
Marcin Oczeretko6a2e5242018-11-28 11:08:50 +000066 if (mBatteryStopwatch != null) {
67 mBatteryStopwatch.close();
68 }
69
Marcin Oczeretkoc80c81a2018-08-30 20:15:52 +010070 mDeviceState = deviceState;
Marcin Oczeretko6a2e5242018-11-28 11:08:50 +000071 mBatteryStopwatch = deviceState.createTimeOnBatteryStopwatch();
Marcin Oczeretkoc80c81a2018-08-30 20:15:52 +010072 }
73
Marcin Oczeretkod32a51352018-11-20 18:11:37 +000074 public void setAddDebugEntries(boolean addDebugEntries) {
75 mAddDebugEntries = addDebugEntries;
76 }
77
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +010078 @Override
79 public Object messageDispatchStarting() {
Marcin Oczeretkoc80c81a2018-08-30 20:15:52 +010080 if (deviceStateAllowsCollection() && shouldCollectDetailedData()) {
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +010081 DispatchSession session = mSessionPool.poll();
82 session = session == null ? new DispatchSession() : session;
83 session.startTimeMicro = getElapsedRealtimeMicro();
84 session.cpuStartMicro = getThreadTimeMicro();
Marcin Oczeretko44272722018-09-19 11:01:32 +010085 session.systemUptimeMillis = getSystemUptimeMillis();
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +010086 return session;
87 }
88
89 return DispatchSession.NOT_SAMPLED;
90 }
91
92 @Override
93 public void messageDispatched(Object token, Message msg) {
Marcin Oczeretkoc80c81a2018-08-30 20:15:52 +010094 if (!deviceStateAllowsCollection()) {
95 return;
96 }
97
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +010098 DispatchSession session = (DispatchSession) token;
Marcin Oczeretkoa674bc92018-09-13 18:20:21 +010099 Entry entry = findEntry(msg, /* allowCreateNew= */session != DispatchSession.NOT_SAMPLED);
100 if (entry != null) {
101 synchronized (entry) {
102 entry.messageCount++;
103 if (session != DispatchSession.NOT_SAMPLED) {
104 entry.recordedMessageCount++;
Marcin Oczeretko44272722018-09-19 11:01:32 +0100105 final long latency = getElapsedRealtimeMicro() - session.startTimeMicro;
106 final long cpuUsage = getThreadTimeMicro() - session.cpuStartMicro;
Marcin Oczeretkoa674bc92018-09-13 18:20:21 +0100107 entry.totalLatencyMicro += latency;
108 entry.maxLatencyMicro = Math.max(entry.maxLatencyMicro, latency);
109 entry.cpuUsageMicro += cpuUsage;
110 entry.maxCpuUsageMicro = Math.max(entry.maxCpuUsageMicro, cpuUsage);
Marcin Oczeretko44272722018-09-19 11:01:32 +0100111 if (msg.getWhen() > 0) {
112 final long delay = Math.max(0L, session.systemUptimeMillis - msg.getWhen());
113 entry.delayMillis += delay;
114 entry.maxDelayMillis = Math.max(entry.maxDelayMillis, delay);
115 entry.recordedDelayMessageCount++;
116 }
Marcin Oczeretkoa674bc92018-09-13 18:20:21 +0100117 }
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100118 }
119 }
120
121 recycleSession(session);
122 }
123
124 @Override
125 public void dispatchingThrewException(Object token, Message msg, Exception exception) {
Marcin Oczeretkoc80c81a2018-08-30 20:15:52 +0100126 if (!deviceStateAllowsCollection()) {
127 return;
128 }
129
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100130 DispatchSession session = (DispatchSession) token;
Olivier Gaillard99ab9532019-02-04 10:47:48 +0000131 Entry entry = findEntry(msg, /* allowCreateNew= */session != DispatchSession.NOT_SAMPLED);
132 if (entry != null) {
133 synchronized (entry) {
134 entry.exceptionCount++;
135 }
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100136 }
Marcin Oczeretko3e6494e2018-09-10 18:06:52 +0100137
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100138 recycleSession(session);
139 }
140
Marcin Oczeretkoc80c81a2018-08-30 20:15:52 +0100141 private boolean deviceStateAllowsCollection() {
142 // Do not collect data if on charger or the state is not set.
143 return mDeviceState != null && !mDeviceState.isCharging();
144 }
145
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100146 /** Returns an array of {@link ExportedEntry entries} with the aggregated statistics. */
147 public List<ExportedEntry> getEntries() {
Marcin Oczeretko3e6494e2018-09-10 18:06:52 +0100148 final ArrayList<ExportedEntry> exportedEntries;
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100149 synchronized (mLock) {
150 final int size = mEntries.size();
Marcin Oczeretko3e6494e2018-09-10 18:06:52 +0100151 exportedEntries = new ArrayList<>(size);
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100152 for (int i = 0; i < size; i++) {
153 Entry entry = mEntries.valueAt(i);
154 synchronized (entry) {
Marcin Oczeretko3e6494e2018-09-10 18:06:52 +0100155 exportedEntries.add(new ExportedEntry(entry));
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100156 }
157 }
158 }
159 // Add the overflow and collision entries only if they have any data.
Marcin Oczeretko3e6494e2018-09-10 18:06:52 +0100160 maybeAddSpecialEntry(exportedEntries, mOverflowEntry);
161 maybeAddSpecialEntry(exportedEntries, mHashCollisionEntry);
Marcin Oczeretkod32a51352018-11-20 18:11:37 +0000162 // Debug entries added to help validate the data.
Marcin Oczeretko6a2e5242018-11-28 11:08:50 +0000163 if (mAddDebugEntries && mBatteryStopwatch != null) {
Olivier Gaillard28109b52018-12-14 15:14:14 +0000164 exportedEntries.add(createDebugEntry("start_time_millis", mStartElapsedTime));
165 exportedEntries.add(createDebugEntry("end_time_millis", SystemClock.elapsedRealtime()));
Marcin Oczeretko6a2e5242018-11-28 11:08:50 +0000166 exportedEntries.add(
167 createDebugEntry("battery_time_millis", mBatteryStopwatch.getMillis()));
Marcin Oczeretkod32a51352018-11-20 18:11:37 +0000168 }
Marcin Oczeretko3e6494e2018-09-10 18:06:52 +0100169 return exportedEntries;
170 }
171
Marcin Oczeretkod32a51352018-11-20 18:11:37 +0000172 private ExportedEntry createDebugEntry(String variableName, long value) {
Marcin Oczeretko94501712018-12-17 18:03:45 +0000173 final Entry entry = new Entry(DEBUG_ENTRY_PREFIX + variableName);
Marcin Oczeretkod32a51352018-11-20 18:11:37 +0000174 entry.messageCount = 1;
175 entry.recordedMessageCount = 1;
Marcin Oczeretko8d861742018-12-06 11:13:29 +0000176 entry.totalLatencyMicro = value;
Marcin Oczeretkod32a51352018-11-20 18:11:37 +0000177 return new ExportedEntry(entry);
178 }
179
Marcin Oczeretkod464bcf2018-10-19 10:46:21 +0100180 /** Returns a timestamp indicating when the statistics were last reset. */
181 public long getStartTimeMillis() {
Olivier Gaillard28109b52018-12-14 15:14:14 +0000182 return mStartCurrentTime;
183 }
184
185 public long getStartElapsedTimeMillis() {
186 return mStartElapsedTime;
Marcin Oczeretkod464bcf2018-10-19 10:46:21 +0100187 }
188
Marcin Oczeretko6a2e5242018-11-28 11:08:50 +0000189 public long getBatteryTimeMillis() {
190 return mBatteryStopwatch != null ? mBatteryStopwatch.getMillis() : 0;
191 }
192
Marcin Oczeretko3e6494e2018-09-10 18:06:52 +0100193 private void maybeAddSpecialEntry(List<ExportedEntry> exportedEntries, Entry specialEntry) {
194 synchronized (specialEntry) {
195 if (specialEntry.messageCount > 0 || specialEntry.exceptionCount > 0) {
196 exportedEntries.add(new ExportedEntry(specialEntry));
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100197 }
198 }
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100199 }
200
201 /** Removes all collected data. */
202 public void reset() {
203 synchronized (mLock) {
204 mEntries.clear();
205 }
206 synchronized (mHashCollisionEntry) {
207 mHashCollisionEntry.reset();
208 }
209 synchronized (mOverflowEntry) {
210 mOverflowEntry.reset();
211 }
Olivier Gaillard28109b52018-12-14 15:14:14 +0000212 mStartCurrentTime = System.currentTimeMillis();
213 mStartElapsedTime = SystemClock.elapsedRealtime();
Marcin Oczeretko6a2e5242018-11-28 11:08:50 +0000214 if (mBatteryStopwatch != null) {
215 mBatteryStopwatch.reset();
216 }
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100217 }
218
Marcin Oczeretko3680ae62018-08-23 16:33:34 +0100219 public void setSamplingInterval(int samplingInterval) {
220 mSamplingInterval = samplingInterval;
221 }
222
Olivier Gaillard36b80ca2019-02-11 11:41:39 +0000223 public void setTrackScreenInteractive(boolean enabled) {
224 mTrackScreenInteractive = enabled;
225 }
226
Marcin Oczeretkoa674bc92018-09-13 18:20:21 +0100227 @Nullable
228 private Entry findEntry(Message msg, boolean allowCreateNew) {
Olivier Gaillard36b80ca2019-02-11 11:41:39 +0000229 final boolean isInteractive = mTrackScreenInteractive
230 ? mDeviceState.isScreenInteractive()
231 : DISABLED_SCREEN_STATE_TRACKING_VALUE;
Marcin Oczeretkoc80c81a2018-08-30 20:15:52 +0100232 final int id = Entry.idFor(msg, isInteractive);
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100233 Entry entry;
234 synchronized (mLock) {
235 entry = mEntries.get(id);
236 if (entry == null) {
Marcin Oczeretkoa674bc92018-09-13 18:20:21 +0100237 if (!allowCreateNew) {
238 return null;
239 } else if (mEntries.size() >= mEntriesSizeCap) {
240 // If over the size cap track totals under OVERFLOW entry.
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100241 return mOverflowEntry;
Marcin Oczeretkoa674bc92018-09-13 18:20:21 +0100242 } else {
243 entry = new Entry(msg, isInteractive);
244 mEntries.put(id, entry);
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100245 }
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100246 }
247 }
248
Marcin Oczeretkoec758722018-09-12 12:53:47 +0100249 if (entry.workSourceUid != msg.workSourceUid
250 || entry.handler.getClass() != msg.getTarget().getClass()
Marcin Oczeretkoc80c81a2018-08-30 20:15:52 +0100251 || entry.handler.getLooper().getThread() != msg.getTarget().getLooper().getThread()
252 || entry.isInteractive != isInteractive) {
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100253 // If a hash collision happened, track totals under a single entry.
254 return mHashCollisionEntry;
255 }
256 return entry;
257 }
258
259 private void recycleSession(DispatchSession session) {
Marcin Oczeretkoa674bc92018-09-13 18:20:21 +0100260 if (session != DispatchSession.NOT_SAMPLED && mSessionPool.size() < SESSION_POOL_SIZE) {
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100261 mSessionPool.add(session);
262 }
263 }
264
265 protected long getThreadTimeMicro() {
266 return SystemClock.currentThreadTimeMicro();
267 }
268
269 protected long getElapsedRealtimeMicro() {
270 return SystemClock.elapsedRealtimeNanos() / 1000;
271 }
272
Marcin Oczeretko44272722018-09-19 11:01:32 +0100273 protected long getSystemUptimeMillis() {
274 return SystemClock.uptimeMillis();
275 }
276
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100277 protected boolean shouldCollectDetailedData() {
278 return ThreadLocalRandom.current().nextInt() % mSamplingInterval == 0;
279 }
280
281 private static class DispatchSession {
282 static final DispatchSession NOT_SAMPLED = new DispatchSession();
283 public long startTimeMicro;
284 public long cpuStartMicro;
Marcin Oczeretko44272722018-09-19 11:01:32 +0100285 public long systemUptimeMillis;
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100286 }
287
288 private static class Entry {
Marcin Oczeretkoec758722018-09-12 12:53:47 +0100289 public final int workSourceUid;
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100290 public final Handler handler;
291 public final String messageName;
Marcin Oczeretkoc80c81a2018-08-30 20:15:52 +0100292 public final boolean isInteractive;
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100293 public long messageCount;
294 public long recordedMessageCount;
295 public long exceptionCount;
296 public long totalLatencyMicro;
297 public long maxLatencyMicro;
298 public long cpuUsageMicro;
299 public long maxCpuUsageMicro;
Marcin Oczeretko44272722018-09-19 11:01:32 +0100300 public long recordedDelayMessageCount;
301 public long delayMillis;
302 public long maxDelayMillis;
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100303
Marcin Oczeretkoc80c81a2018-08-30 20:15:52 +0100304 Entry(Message msg, boolean isInteractive) {
Marcin Oczeretkoec758722018-09-12 12:53:47 +0100305 this.workSourceUid = msg.workSourceUid;
Marcin Oczeretkoc80c81a2018-08-30 20:15:52 +0100306 this.handler = msg.getTarget();
307 this.messageName = handler.getMessageName(msg);
308 this.isInteractive = isInteractive;
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100309 }
310
311 Entry(String specialEntryName) {
Marcin Oczeretkoec758722018-09-12 12:53:47 +0100312 this.workSourceUid = Message.UID_NONE;
Marcin Oczeretkoc80c81a2018-08-30 20:15:52 +0100313 this.messageName = specialEntryName;
314 this.handler = null;
315 this.isInteractive = false;
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100316 }
317
318 void reset() {
319 messageCount = 0;
320 recordedMessageCount = 0;
321 exceptionCount = 0;
322 totalLatencyMicro = 0;
323 maxLatencyMicro = 0;
324 cpuUsageMicro = 0;
325 maxCpuUsageMicro = 0;
Marcin Oczeretko44272722018-09-19 11:01:32 +0100326 delayMillis = 0;
327 maxDelayMillis = 0;
328 recordedDelayMessageCount = 0;
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100329 }
330
Marcin Oczeretkoc80c81a2018-08-30 20:15:52 +0100331 static int idFor(Message msg, boolean isInteractive) {
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100332 int result = 7;
Marcin Oczeretkoec758722018-09-12 12:53:47 +0100333 result = 31 * result + msg.workSourceUid;
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100334 result = 31 * result + msg.getTarget().getLooper().getThread().hashCode();
335 result = 31 * result + msg.getTarget().getClass().hashCode();
Marcin Oczeretkoc80c81a2018-08-30 20:15:52 +0100336 result = 31 * result + (isInteractive ? 1231 : 1237);
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100337 if (msg.getCallback() != null) {
338 return 31 * result + msg.getCallback().getClass().hashCode();
339 } else {
340 return 31 * result + msg.what;
341 }
342 }
343 }
344
345 /** Aggregated data of Looper message dispatching in the in the current process. */
346 public static class ExportedEntry {
Marcin Oczeretkoec758722018-09-12 12:53:47 +0100347 public final int workSourceUid;
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100348 public final String handlerClassName;
349 public final String threadName;
350 public final String messageName;
Marcin Oczeretkoc80c81a2018-08-30 20:15:52 +0100351 public final boolean isInteractive;
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100352 public final long messageCount;
353 public final long recordedMessageCount;
354 public final long exceptionCount;
355 public final long totalLatencyMicros;
356 public final long maxLatencyMicros;
357 public final long cpuUsageMicros;
358 public final long maxCpuUsageMicros;
Marcin Oczeretko44272722018-09-19 11:01:32 +0100359 public final long maxDelayMillis;
360 public final long delayMillis;
361 public final long recordedDelayMessageCount;
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100362
363 ExportedEntry(Entry entry) {
Marcin Oczeretkoec758722018-09-12 12:53:47 +0100364 this.workSourceUid = entry.workSourceUid;
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100365 if (entry.handler != null) {
366 this.handlerClassName = entry.handler.getClass().getName();
367 this.threadName = entry.handler.getLooper().getThread().getName();
368 } else {
369 // Overflow/collision entries do not have a handler set.
370 this.handlerClassName = "";
371 this.threadName = "";
372 }
Marcin Oczeretkoc80c81a2018-08-30 20:15:52 +0100373 this.isInteractive = entry.isInteractive;
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100374 this.messageName = entry.messageName;
375 this.messageCount = entry.messageCount;
376 this.recordedMessageCount = entry.recordedMessageCount;
377 this.exceptionCount = entry.exceptionCount;
378 this.totalLatencyMicros = entry.totalLatencyMicro;
379 this.maxLatencyMicros = entry.maxLatencyMicro;
380 this.cpuUsageMicros = entry.cpuUsageMicro;
381 this.maxCpuUsageMicros = entry.maxCpuUsageMicro;
Marcin Oczeretko44272722018-09-19 11:01:32 +0100382 this.delayMillis = entry.delayMillis;
383 this.maxDelayMillis = entry.maxDelayMillis;
384 this.recordedDelayMessageCount = entry.recordedDelayMessageCount;
Marcin Oczeretkod8cc8592018-08-22 16:07:36 +0100385 }
386 }
387}