• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.internal.os;
18 
19 import android.annotation.Nullable;
20 import android.compat.annotation.UnsupportedAppUsage;
21 import android.content.Intent;
22 import android.content.pm.ServiceInfo;
23 import android.util.Log;
24 
25 import com.android.internal.annotations.VisibleForTesting;
26 
27 import java.util.Collections;
28 import java.util.List;
29 import java.util.Objects;
30 
31 
32 /**
33  * The DebugStore class provides methods for recording various debug events related to service
34  * lifecycle, broadcast receivers and others.
35  * The DebugStore class facilitates debugging ANR issues by recording time-stamped events
36  * related to service lifecycles, broadcast receivers, and other framework operations. It logs
37  * the start and end times of operations within the ANR timer scope called  by framework,
38  * enabling pinpointing of methods and events contributing to ANRs.
39  *
40  * Usage currently includes recording service starts, binds, and asynchronous operations initiated
41  * by broadcast receivers, providing a granular view of system behavior that facilitates
42  * identifying performance bottlenecks and optimizing issue resolution.
43  *
44  * @hide
45  */
46 public class DebugStore {
47     private static final boolean DEBUG_EVENTS = false;
48     private static final String TAG = "DebugStore";
49 
50     private static DebugStoreNative sDebugStoreNative = new DebugStoreNativeImpl();
51 
52     @UnsupportedAppUsage
53     @VisibleForTesting
setDebugStoreNative(DebugStoreNative nativeImpl)54     public static void setDebugStoreNative(DebugStoreNative nativeImpl) {
55         sDebugStoreNative = nativeImpl;
56     }
57     /**
58      * Records the start of a service.
59      *
60      * @param startId The start ID of the service.
61      * @param flags Additional flags for the service start.
62      * @param intent The Intent associated with the service start.
63      * @return A unique ID for the recorded event.
64      */
65     @UnsupportedAppUsage
recordServiceOnStart(int startId, int flags, @Nullable Intent intent)66     public static long recordServiceOnStart(int startId, int flags, @Nullable Intent intent) {
67         return sDebugStoreNative.beginEvent(
68                 "SvcStart",
69                 List.of(
70                         "stId",
71                         String.valueOf(startId),
72                         "flg",
73                         Integer.toHexString(flags),
74                         "act",
75                         Objects.toString(intent != null ? intent.getAction() : null),
76                         "comp",
77                         Objects.toString(intent != null ? intent.getComponent() : null),
78                         "pkg",
79                         Objects.toString(intent != null ? intent.getPackage() : null)));
80     }
81 
82     /**
83      * Records the creation of a service.
84      *
85      * @param serviceInfo Information about the service being created.
86      * @return A unique ID for the recorded event.
87      */
88     @UnsupportedAppUsage
recordServiceCreate(@ullable ServiceInfo serviceInfo)89     public static long recordServiceCreate(@Nullable ServiceInfo serviceInfo) {
90         return sDebugStoreNative.beginEvent(
91                 "SvcCreate",
92                 List.of(
93                         "name",
94                         Objects.toString(serviceInfo != null ? serviceInfo.name : null),
95                         "pkg",
96                         Objects.toString(serviceInfo != null ? serviceInfo.packageName : null)));
97     }
98 
99     /**
100      * Records the binding of a service.
101      *
102      * @param isRebind Indicates whether the service is being rebound.
103      * @param intent The Intent associated with the service binding.
104      * @return A unique identifier for the recorded event.
105      */
106     @UnsupportedAppUsage
recordServiceBind(boolean isRebind, @Nullable Intent intent)107     public static long recordServiceBind(boolean isRebind, @Nullable Intent intent) {
108         return sDebugStoreNative.beginEvent(
109                 "SvcBind",
110                 List.of(
111                         "rebind",
112                         String.valueOf(isRebind),
113                         "act",
114                         Objects.toString(intent != null ? intent.getAction() : null),
115                         "cmp",
116                         Objects.toString(intent != null ? intent.getComponent() : null),
117                         "pkg",
118                         Objects.toString(intent != null ? intent.getPackage() : null)));
119     }
120 
121     /**
122      * Records an asynchronous operation initiated by a broadcast receiver through calling GoAsync.
123      *
124      * @param receiverClassName The class name of the broadcast receiver.
125      */
126     @UnsupportedAppUsage
recordGoAsync(int pendingResultId)127     public static void recordGoAsync(int pendingResultId) {
128         sDebugStoreNative.recordEvent(
129                 "GoAsync",
130                 List.of(
131                         "tname",
132                         Thread.currentThread().getName(),
133                         "tid",
134                         String.valueOf(Thread.currentThread().getId()),
135                         "prid",
136                         Integer.toHexString(pendingResultId)));
137     }
138 
139     /**
140      * Records the completion of a broadcast operation through calling Finish.
141      *
142      * @param receiverClassName The class of the broadcast receiver that completed the operation.
143      */
144     @UnsupportedAppUsage
recordFinish(int pendingResultId)145     public static void recordFinish(int pendingResultId) {
146         sDebugStoreNative.recordEvent(
147                 "Finish",
148                 List.of(
149                         "tname",
150                         Thread.currentThread().getName(),
151                         "tid",
152                         String.valueOf(Thread.currentThread().getId()),
153                         "prid",
154                         Integer.toHexString(pendingResultId)));
155     }
156 
157     /**
158      * Records the completion of a long-running looper message.
159      *
160      * @param messageCode The code representing the type of the message.
161      * @param targetClass The FQN of the class that handled the message.
162      * @param elapsedTimeMs The time that was taken to process the message, in milliseconds.
163      */
164     @UnsupportedAppUsage
recordLongLooperMessage(int messageCode, String targetClass, long elapsedTimeMs)165     public static void recordLongLooperMessage(int messageCode, String targetClass,
166             long elapsedTimeMs) {
167         sDebugStoreNative.recordEvent(
168                 "LooperMsg",
169                 List.of(
170                         "code",
171                         String.valueOf(messageCode),
172                         "trgt",
173                         Objects.toString(targetClass),
174                         "elapsed",
175                         String.valueOf(elapsedTimeMs)));
176     }
177 
178 
179     /**
180      * Records the reception of a broadcast by a manifest-declared receiver.
181      *
182      * @param intent The Intent associated with the broadcast.
183      * @return A unique ID for the recorded event.
184      */
185     @UnsupportedAppUsage
recordBroadcastReceive(@ullable Intent intent, int pendingResultId)186     public static long recordBroadcastReceive(@Nullable Intent intent, int pendingResultId) {
187         return sDebugStoreNative.beginEvent(
188                 "BcRcv",
189                 List.of(
190                         "tname",
191                         Thread.currentThread().getName(),
192                         "tid",
193                         String.valueOf(Thread.currentThread().getId()),
194                         "act",
195                         Objects.toString(intent != null ? intent.getAction() : null),
196                         "cmp",
197                         Objects.toString(intent != null ? intent.getComponent() : null),
198                         "pkg",
199                         Objects.toString(intent != null ? intent.getPackage() : null),
200                         "prid",
201                         Integer.toHexString(pendingResultId)));
202     }
203 
204     /**
205      * Records the reception of a broadcast by a context-registered receiver.
206      *
207      * @param intent The Intent associated with the broadcast.
208      * @param pendingResultId The object ID of the PendingResult associated with the broadcast.
209      * @return A unique ID for the recorded event.
210      */
211     @UnsupportedAppUsage
recordBroadcastReceiveReg(@ullable Intent intent, int pendingResultId)212     public static long recordBroadcastReceiveReg(@Nullable Intent intent, int pendingResultId) {
213         return sDebugStoreNative.beginEvent(
214                 "BcRcvReg",
215                 List.of(
216                         "tname",
217                         Thread.currentThread().getName(),
218                         "tid",
219                         String.valueOf(Thread.currentThread().getId()),
220                         "act",
221                         Objects.toString(intent != null ? intent.getAction() : null),
222                         "cmp",
223                         Objects.toString(intent != null ? intent.getComponent() : null),
224                         "pkg",
225                         Objects.toString(intent != null ? intent.getPackage() : null),
226                         "prid",
227                         Integer.toHexString(pendingResultId)));
228     }
229 
230     /**
231      * Records the binding of an application.
232      *
233      * @return A unique ID for the recorded event.
234      */
235     @UnsupportedAppUsage
recordHandleBindApplication()236     public static long recordHandleBindApplication() {
237         return sDebugStoreNative.beginEvent("BindApp", List.of());
238     }
239 
240     /**
241      * Records the scheduling of a receiver.
242      *
243      * @return A unique ID for the recorded event.
244      */
245     @UnsupportedAppUsage
recordScheduleReceiver()246     public static long recordScheduleReceiver() {
247         return sDebugStoreNative.beginEvent(
248                 "SchRcv",
249                 List.of(
250                         "tname", Thread.currentThread().getName(),
251                         "tid", String.valueOf(Thread.currentThread().getId())));
252     }
253 
254     /**
255      * Records the scheduling of a registered receiver.
256      *
257      * @return A unique ID for the recorded event.
258      */
259     @UnsupportedAppUsage
recordScheduleRegisteredReceiver()260     public static long recordScheduleRegisteredReceiver() {
261         return sDebugStoreNative.beginEvent(
262                 "SchRcvReg",
263                 List.of(
264                         "tname", Thread.currentThread().getName(),
265                         "tid", String.valueOf(Thread.currentThread().getId())));
266     }
267 
268     /**
269      * Ends a previously recorded event.
270      *
271      * @param id The unique ID of the event to be ended.
272      */
273     @UnsupportedAppUsage
recordEventEnd(long id)274     public static void recordEventEnd(long id) {
275         sDebugStoreNative.endEvent(id, Collections.emptyList());
276     }
277 
278     /**
279      * An interface for a class that acts as a wrapper for the static native methods
280      * of the Debug Store.
281      *
282      * It allows us to mock static native methods in our tests and should be removed
283      * once mocking static methods becomes easier.
284      */
285     @VisibleForTesting
286     public interface DebugStoreNative {
287         /**
288          * Begins an event with the given name and attributes.
289          */
beginEvent(String eventName, List<String> attributes)290         long beginEvent(String eventName, List<String> attributes);
291         /**
292          * Ends an event with the given ID and attributes.
293          */
endEvent(long id, List<String> attributes)294         void endEvent(long id, List<String> attributes);
295         /**
296          * Records an event with the given name and attributes.
297          */
recordEvent(String eventName, List<String> attributes)298         void recordEvent(String eventName, List<String> attributes);
299     }
300 
301     private static class DebugStoreNativeImpl implements DebugStoreNative {
302         @Override
beginEvent(String eventName, List<String> attributes)303         public long beginEvent(String eventName, List<String> attributes) {
304             long id = DebugStore.beginEventNative(eventName, attributes);
305             if (DEBUG_EVENTS) {
306                 Log.i(
307                         TAG,
308                         "beginEvent: " + id + " " + eventName + " " + attributeString(attributes));
309             }
310             return id;
311         }
312 
313         @Override
endEvent(long id, List<String> attributes)314         public void endEvent(long id, List<String> attributes) {
315             if (DEBUG_EVENTS) {
316                 Log.i(TAG, "endEvent: " + id + " " + attributeString(attributes));
317             }
318             DebugStore.endEventNative(id, attributes);
319         }
320 
321         @Override
recordEvent(String eventName, List<String> attributes)322         public void recordEvent(String eventName, List<String> attributes) {
323             if (DEBUG_EVENTS) {
324                 Log.i(TAG, "recordEvent: " + eventName + " " + attributeString(attributes));
325             }
326             DebugStore.recordEventNative(eventName, attributes);
327         }
328 
329         /**
330          * Returns a string like "[key1=foo, key2=bar]"
331          */
attributeString(List<String> attributes)332         private String attributeString(List<String> attributes) {
333             StringBuilder sb = new StringBuilder().append("[");
334 
335             for (int i = 0; i < attributes.size(); i++) {
336                 sb.append(attributes.get(i));
337 
338                 if (i % 2 == 0) {
339                     sb.append("=");
340                 } else if (i < attributes.size() - 1) {
341                     sb.append(", ");
342                 }
343             }
344             return sb.append("]").toString();
345         }
346     }
347 
beginEventNative(String eventName, List<String> attributes)348     private static native long beginEventNative(String eventName, List<String> attributes);
349 
endEventNative(long id, List<String> attributes)350     private static native void endEventNative(long id, List<String> attributes);
351 
recordEventNative(String eventName, List<String> attributes)352     private static native void recordEventNative(String eventName, List<String> attributes);
353 }
354