1 /*
2  * Copyright (C) 2022 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.server.am;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.content.Intent;
22 import android.os.Bundle;
23 import android.util.TimeUtils;
24 import android.util.proto.ProtoOutputStream;
25 
26 import dalvik.annotation.optimization.NeverCompile;
27 
28 import java.io.PrintWriter;
29 import java.text.SimpleDateFormat;
30 import java.util.ArrayList;
31 import java.util.Date;
32 
33 /**
34  * Collection of recent historical broadcasts that are available to be dumped
35  * for debugging purposes. Automatically trims itself over time.
36  */
37 public class BroadcastHistory {
38     private final int MAX_BROADCAST_HISTORY;
39     private final int MAX_BROADCAST_SUMMARY_HISTORY;
40 
BroadcastHistory(@onNull BroadcastConstants constants)41     public BroadcastHistory(@NonNull BroadcastConstants constants) {
42         MAX_BROADCAST_HISTORY = constants.MAX_HISTORY_COMPLETE_SIZE;
43         MAX_BROADCAST_SUMMARY_HISTORY = constants.MAX_HISTORY_SUMMARY_SIZE;
44 
45         mBroadcastHistory = new BroadcastRecord[MAX_BROADCAST_HISTORY];
46         mBroadcastSummaryHistory = new Intent[MAX_BROADCAST_SUMMARY_HISTORY];
47         mSummaryHistoryEnqueueTime = new long[MAX_BROADCAST_SUMMARY_HISTORY];
48         mSummaryHistoryDispatchTime = new long[MAX_BROADCAST_SUMMARY_HISTORY];
49         mSummaryHistoryFinishTime = new long[MAX_BROADCAST_SUMMARY_HISTORY];
50     }
51 
52     /**
53      * List of broadcasts in frozen processes that are yet to be enqueued.
54      */
55     private final ArrayList<BroadcastRecord> mFrozenBroadcasts = new ArrayList<>();
56 
57     /**
58      * List of broadcasts which are being delivered or yet to be delivered.
59      */
60     private final ArrayList<BroadcastRecord> mPendingBroadcasts = new ArrayList<>();
61 
62     /**
63      * Historical data of past broadcasts, for debugging.  This is a ring buffer
64      * whose last element is at mHistoryNext.
65      */
66     final BroadcastRecord[] mBroadcastHistory;
67     int mHistoryNext = 0;
68 
69     /**
70      * Summary of historical data of past broadcasts, for debugging.  This is a
71      * ring buffer whose last element is at mSummaryHistoryNext.
72      */
73     final Intent[] mBroadcastSummaryHistory;
74     int mSummaryHistoryNext = 0;
75 
76     /**
77      * Various milestone timestamps of entries in the mBroadcastSummaryHistory ring
78      * buffer, also tracked via the mSummaryHistoryNext index.  These are all in wall
79      * clock time, not elapsed.
80      */
81     final long[] mSummaryHistoryEnqueueTime;
82     final long[] mSummaryHistoryDispatchTime;
83     final long[] mSummaryHistoryFinishTime;
84 
onBroadcastFrozenLocked(@onNull BroadcastRecord r)85     void onBroadcastFrozenLocked(@NonNull BroadcastRecord r) {
86         mFrozenBroadcasts.add(r);
87     }
88 
onBroadcastEnqueuedLocked(@onNull BroadcastRecord r)89     void onBroadcastEnqueuedLocked(@NonNull BroadcastRecord r) {
90         mFrozenBroadcasts.remove(r);
91         mPendingBroadcasts.add(r);
92     }
93 
onBroadcastFinishedLocked(@onNull BroadcastRecord r)94     void onBroadcastFinishedLocked(@NonNull BroadcastRecord r) {
95         mPendingBroadcasts.remove(r);
96         addBroadcastToHistoryLocked(r);
97     }
98 
addBroadcastToHistoryLocked(@onNull BroadcastRecord original)99     public void addBroadcastToHistoryLocked(@NonNull BroadcastRecord original) {
100         // Note sometimes (only for sticky broadcasts?) we reuse BroadcastRecords,
101         // So don't change the incoming record directly.
102         final BroadcastRecord historyRecord = original.maybeStripForHistory();
103 
104         mBroadcastHistory[mHistoryNext] = historyRecord;
105         mHistoryNext = ringAdvance(mHistoryNext, 1, MAX_BROADCAST_HISTORY);
106 
107         mBroadcastSummaryHistory[mSummaryHistoryNext] = historyRecord.intent;
108         mSummaryHistoryEnqueueTime[mSummaryHistoryNext] = historyRecord.enqueueClockTime;
109         mSummaryHistoryDispatchTime[mSummaryHistoryNext] = historyRecord.dispatchClockTime;
110         mSummaryHistoryFinishTime[mSummaryHistoryNext] = System.currentTimeMillis();
111         mSummaryHistoryNext = ringAdvance(mSummaryHistoryNext, 1, MAX_BROADCAST_SUMMARY_HISTORY);
112     }
113 
ringAdvance(int x, final int increment, final int ringSize)114     private int ringAdvance(int x, final int increment, final int ringSize) {
115         x += increment;
116         if (x < 0) return (ringSize - 1);
117         else if (x >= ringSize) return 0;
118         else return x;
119     }
120 
121     @NeverCompile
dumpDebug(@onNull ProtoOutputStream proto)122     public void dumpDebug(@NonNull ProtoOutputStream proto) {
123         for (int i = 0; i < mPendingBroadcasts.size(); ++i) {
124             final BroadcastRecord r = mPendingBroadcasts.get(i);
125             r.dumpDebug(proto, BroadcastQueueProto.PENDING_BROADCASTS);
126         }
127         for (int i = 0; i < mFrozenBroadcasts.size(); ++i) {
128             final BroadcastRecord r = mFrozenBroadcasts.get(i);
129             r.dumpDebug(proto, BroadcastQueueProto.FROZEN_BROADCASTS);
130         }
131 
132         int lastIndex = mHistoryNext;
133         int ringIndex = lastIndex;
134         do {
135             // increasing index = more recent entry, and we want to print the most
136             // recent first and work backwards, so we roll through the ring backwards.
137             ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_HISTORY);
138             BroadcastRecord r = mBroadcastHistory[ringIndex];
139             if (r != null) {
140                 r.dumpDebug(proto, BroadcastQueueProto.HISTORICAL_BROADCASTS);
141             }
142         } while (ringIndex != lastIndex);
143 
144         lastIndex = ringIndex = mSummaryHistoryNext;
145         do {
146             ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_SUMMARY_HISTORY);
147             Intent intent = mBroadcastSummaryHistory[ringIndex];
148             if (intent == null) {
149                 continue;
150             }
151             long summaryToken = proto.start(BroadcastQueueProto.HISTORICAL_BROADCASTS_SUMMARY);
152             intent.dumpDebug(proto, BroadcastQueueProto.BroadcastSummary.INTENT,
153                     false, true, true, false);
154             proto.write(BroadcastQueueProto.BroadcastSummary.ENQUEUE_CLOCK_TIME_MS,
155                     mSummaryHistoryEnqueueTime[ringIndex]);
156             proto.write(BroadcastQueueProto.BroadcastSummary.DISPATCH_CLOCK_TIME_MS,
157                     mSummaryHistoryDispatchTime[ringIndex]);
158             proto.write(BroadcastQueueProto.BroadcastSummary.FINISH_CLOCK_TIME_MS,
159                     mSummaryHistoryFinishTime[ringIndex]);
160             proto.end(summaryToken);
161         } while (ringIndex != lastIndex);
162     }
163 
164     @NeverCompile
dumpLocked(@onNull PrintWriter pw, @Nullable String dumpPackage, @NonNull String queueName, @NonNull SimpleDateFormat sdf, boolean dumpAll, boolean needSep)165     public boolean dumpLocked(@NonNull PrintWriter pw, @Nullable String dumpPackage,
166             @NonNull String queueName, @NonNull SimpleDateFormat sdf,
167             boolean dumpAll, boolean needSep) {
168         dumpBroadcastList(pw, sdf, mFrozenBroadcasts, "Frozen");
169         dumpBroadcastList(pw, sdf, mPendingBroadcasts, "Pending");
170 
171         int i;
172         boolean printed = false;
173 
174         i = -1;
175         int lastIndex = mHistoryNext;
176         int ringIndex = lastIndex;
177         do {
178             // increasing index = more recent entry, and we want to print the most
179             // recent first and work backwards, so we roll through the ring backwards.
180             ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_HISTORY);
181             BroadcastRecord r = mBroadcastHistory[ringIndex];
182             if (r == null) {
183                 continue;
184             }
185 
186             i++; // genuine record of some sort even if we're filtering it out
187             if (dumpPackage != null && !dumpPackage.equals(r.callerPackage)) {
188                 continue;
189             }
190             if (!printed) {
191                 if (needSep) {
192                     pw.println();
193                 }
194                 needSep = true;
195                 pw.println("  Historical broadcasts [" + queueName + "]:");
196                 printed = true;
197             }
198             if (dumpAll) {
199                 pw.print("  Historical Broadcast " + queueName + " #");
200                         pw.print(i); pw.println(":");
201                 r.dump(pw, "    ", sdf);
202             } else {
203                 pw.print("  #"); pw.print(i); pw.print(": "); pw.println(r);
204                 pw.print("    ");
205                 pw.println(r.intent.toShortString(false, true, true, false));
206                 if (r.targetComp != null && r.targetComp != r.intent.getComponent()) {
207                     pw.print("    targetComp: "); pw.println(r.targetComp.toShortString());
208                 }
209                 Bundle bundle = r.intent.getExtras();
210                 if (bundle != null) {
211                     pw.print("    extras: "); pw.println(bundle.toString());
212                 }
213             }
214         } while (ringIndex != lastIndex);
215 
216         if (dumpPackage == null) {
217             lastIndex = ringIndex = mSummaryHistoryNext;
218             if (dumpAll) {
219                 printed = false;
220                 i = -1;
221             } else {
222                 // roll over the 'i' full dumps that have already been issued
223                 for (int j = i;
224                         j > 0 && ringIndex != lastIndex;) {
225                     ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_SUMMARY_HISTORY);
226                     BroadcastRecord r = mBroadcastHistory[ringIndex];
227                     if (r == null) {
228                         continue;
229                     }
230                     j--;
231                 }
232             }
233             // done skipping; dump the remainder of the ring. 'i' is still the ordinal within
234             // the overall broadcast history.
235             do {
236                 ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_SUMMARY_HISTORY);
237                 Intent intent = mBroadcastSummaryHistory[ringIndex];
238                 if (intent == null) {
239                     continue;
240                 }
241                 if (!printed) {
242                     if (needSep) {
243                         pw.println();
244                     }
245                     needSep = true;
246                     pw.println("  Historical broadcasts summary [" + queueName + "]:");
247                     printed = true;
248                 }
249                 if (!dumpAll && i >= 50) {
250                     pw.println("  ...");
251                     break;
252                 }
253                 i++;
254                 pw.print("  #"); pw.print(i); pw.print(": ");
255                 pw.println(intent.toShortString(false, true, true, false));
256                 pw.print("    ");
257                 TimeUtils.formatDuration(mSummaryHistoryDispatchTime[ringIndex]
258                         - mSummaryHistoryEnqueueTime[ringIndex], pw);
259                 pw.print(" dispatch ");
260                 TimeUtils.formatDuration(mSummaryHistoryFinishTime[ringIndex]
261                         - mSummaryHistoryDispatchTime[ringIndex], pw);
262                 pw.println(" finish");
263                 pw.print("    enq=");
264                 pw.print(sdf.format(new Date(mSummaryHistoryEnqueueTime[ringIndex])));
265                 pw.print(" disp=");
266                 pw.print(sdf.format(new Date(mSummaryHistoryDispatchTime[ringIndex])));
267                 pw.print(" fin=");
268                 pw.println(sdf.format(new Date(mSummaryHistoryFinishTime[ringIndex])));
269                 Bundle bundle = intent.getExtras();
270                 if (bundle != null) {
271                     pw.print("    extras: "); pw.println(bundle.toString());
272                 }
273             } while (ringIndex != lastIndex);
274         }
275         return needSep;
276     }
277 
dumpBroadcastList(@onNull PrintWriter pw, @NonNull SimpleDateFormat sdf, @NonNull ArrayList<BroadcastRecord> broadcasts, @NonNull String flavor)278     private void dumpBroadcastList(@NonNull PrintWriter pw, @NonNull SimpleDateFormat sdf,
279             @NonNull ArrayList<BroadcastRecord> broadcasts, @NonNull String flavor) {
280         pw.print("  "); pw.print(flavor); pw.println(" broadcasts:");
281         if (broadcasts.isEmpty()) {
282             pw.println("    <empty>");
283         } else {
284             for (int idx = broadcasts.size() - 1; idx >= 0; --idx) {
285                 final BroadcastRecord r = broadcasts.get(idx);
286                 pw.print(flavor); pw.print("  broadcast #"); pw.print(idx); pw.println(":");
287                 r.dump(pw, "    ", sdf);
288             }
289         }
290     }
291 }
292