• 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.internal.os;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.os.SystemClock;
22 import android.util.ArraySet;
23 import android.util.Log;
24 import android.util.SparseArray;
25 
26 import com.android.internal.annotations.GuardedBy;
27 import com.android.internal.util.HeavyHitterSketch;
28 
29 import java.util.ArrayList;
30 import java.util.List;
31 
32 /**
33  * A watcher which makes stats on the incoming binder transaction, if the amount of some type of
34  * transactions exceeds the threshold, the listener will be notified.
35  */
36 public final class BinderCallHeavyHitterWatcher {
37     private static final String TAG = "BinderCallHeavyHitterWatcher";
38 
39     /**
40      * Whether or not this watcher is enabled.
41      */
42     @GuardedBy("mLock")
43     private boolean mEnabled;
44 
45     /**
46      * The listener to be notified in case the amount of some type of transactions exceeds the
47      * threshold.
48      */
49     @GuardedBy("mLock")
50     private BinderCallHeavyHitterListener mListener;
51 
52     /**
53      * The heavy hitter stats.
54      */
55     @GuardedBy("mLock")
56     private HeavyHitterSketch<Integer> mHeavyHitterSketch;
57 
58     /**
59      * The candidates that could be the heavy hitters, so we track their hashcode and the actual
60      * containers in this map.
61      */
62     @GuardedBy("mLock")
63     private final SparseArray<HeavyHitterContainer> mHeavyHitterCandiates = new SparseArray<>();
64 
65     /**
66      * The cache to receive the list of candidates (consists of the hashcode of heavy hitters).
67      */
68     @GuardedBy("mLock")
69     private final ArrayList<Integer> mCachedCandidateList = new ArrayList<>();
70 
71     /**
72      * The cache to receive the frequencies of each items in {@link #mCachedCandidateList}.
73      */
74     @GuardedBy("mLock")
75     private final ArrayList<Float> mCachedCandidateFrequencies = new ArrayList<>();
76 
77     /**
78      * The cache set to host the candidates.
79      */
80     @GuardedBy("mLock")
81     private ArraySet<Integer> mCachedCandidateSet = new ArraySet<>();
82 
83     /**
84      * The cache set to host the containers of candidates.
85      */
86     @GuardedBy("mLock")
87     private HeavyHitterContainer[] mCachedCandidateContainers;
88 
89     /**
90      * The index to the {@link #mCachedCandidateContainers}, denote the first available slot
91      */
92     @GuardedBy("mLock")
93     private int mCachedCandidateContainersIndex;
94 
95     /**
96      * The input size, should be {@link #mTotalInputSize} - validation size.
97      */
98     @GuardedBy("mLock")
99     private int mInputSize;
100 
101     /**
102      * The total input size.
103      */
104     @GuardedBy("mLock")
105     private int mTotalInputSize;
106 
107     /**
108      * The number of inputs so far
109      */
110     @GuardedBy("mLock")
111     private int mCurrentInputSize;
112 
113     /**
114      * The threshold to be considered as heavy hitters
115      */
116     @GuardedBy("mLock")
117     private float mThreshold;
118 
119     /**
120      * The timestamp of the start of current tracing.
121      */
122     @GuardedBy("mLock")
123     private long mBatchStartTimeStamp;
124 
125     /**
126      * The lock object
127      */
128     private final Object mLock = new Object();
129 
130     /**
131      * The tolerance within which is approximately equal
132      */
133     private static final float EPSILON = 0.00001f;
134 
135     /**
136      * Callback interface when the amount of some type of transactions exceeds the threshold.
137      */
138     public interface BinderCallHeavyHitterListener {
139         /**
140          * @param heavyHitters     The list of binder call heavy hitters
141          * @param totalBinderCalls The total binder calls
142          * @param threshold        The threshold to be considered as heavy hitters
143          * @param timeSpan         The toal time span of all these binder calls
144          */
onHeavyHit(List<HeavyHitterContainer> heavyHitters, int totalBinderCalls, float threshold, long timeSpan)145         void onHeavyHit(List<HeavyHitterContainer> heavyHitters,
146                 int totalBinderCalls, float threshold, long timeSpan);
147     }
148 
149     /**
150      * Container to hold the potential heavy hitters
151      */
152     public static final class HeavyHitterContainer {
153         /**
154          * The caller UID
155          */
156         public int mUid;
157 
158         /**
159          * The class of the Binder object which is being hit heavily
160          */
161         public Class mClass;
162 
163         /**
164          * The transaction code within the Binder object which is being hit heavily
165          */
166         public int mCode;
167 
168         /**
169          * The frequency of this being hit (a number between 0...1)
170          */
171         public float mFrequency;
172 
173         /**
174          * Default constructor
175          */
HeavyHitterContainer()176         public HeavyHitterContainer() {
177         }
178 
179         /**
180          * Copy constructor
181          */
HeavyHitterContainer(@onNull final HeavyHitterContainer other)182         public HeavyHitterContainer(@NonNull final HeavyHitterContainer other) {
183             this.mUid = other.mUid;
184             this.mClass = other.mClass;
185             this.mCode = other.mCode;
186             this.mFrequency = other.mFrequency;
187         }
188 
189         @Override
equals(final Object other)190         public boolean equals(final Object other) {
191             if (other == null || !(other instanceof HeavyHitterContainer)) {
192                 return false;
193             }
194             HeavyHitterContainer o = (HeavyHitterContainer) other;
195             return this.mUid == o.mUid && this.mClass == o.mClass && this.mCode == o.mCode
196                     && Math.abs(this.mFrequency - o.mFrequency) < EPSILON;
197         }
198 
199         @Override
hashCode()200         public int hashCode() {
201             return hashCode(mUid, mClass, mCode);
202         }
203 
204         /**
205          * Compute the hashcode with given parameters.
206          */
hashCode(int uid, @NonNull Class clazz, int code)207         static int hashCode(int uid, @NonNull Class clazz, int code) {
208             int hash = uid;
209             hash = 31 * hash + clazz.hashCode();
210             hash = 31 * hash + code;
211             return hash;
212         }
213     }
214 
215     /**
216      * The static lock object
217      */
218     private static final Object sLock = new Object();
219 
220     /**
221      * The default instance
222      */
223     @GuardedBy("sLock")
224     private static BinderCallHeavyHitterWatcher sInstance = null;
225 
226     /**
227      * Return the instance of the watcher
228      */
getInstance()229     public static BinderCallHeavyHitterWatcher getInstance() {
230         synchronized (sLock) {
231             if (sInstance == null) {
232                 sInstance = new BinderCallHeavyHitterWatcher();
233             }
234             return sInstance;
235         }
236     }
237 
238     /**
239      * Configure the parameters.
240      *
241      * @param enable    Whether or not to enable the watcher
242      * @param batchSize The number of binder transactions it needs to receive before the conclusion
243      * @param threshold The threshold to determine if some type of transactions are too many, it
244      *                  should be a value between (0.0f, 1.0f]
245      * @param listener  The callback interface
246      */
setConfig(final boolean enable, final int batchSize, final float threshold, @Nullable final BinderCallHeavyHitterListener listener)247     public void setConfig(final boolean enable, final int batchSize, final float threshold,
248             @Nullable final BinderCallHeavyHitterListener listener) {
249         synchronized (mLock) {
250             if (!enable) {
251                 if (mEnabled) {
252                     resetInternalLocked(null, null, 0, 0, 0.0f, 0);
253                     mEnabled = false;
254                 }
255                 return;
256             }
257             mEnabled = true;
258             // Validate the threshold, which is expected to be within (0.0f, 1.0f]
259             if (threshold < EPSILON || threshold > 1.0f) {
260                 return;
261             }
262 
263             if (batchSize == mTotalInputSize && Math.abs(threshold - mThreshold) < EPSILON) {
264                 // Shortcut: just update the listener, no need to reset the watcher itself.
265                 mListener = listener;
266                 return;
267             }
268 
269             final int capacity = (int) (1.0f / threshold);
270             final HeavyHitterSketch<Integer> sketch = HeavyHitterSketch.<Integer>newDefault();
271             final float validationRatio = sketch.getRequiredValidationInputRatio();
272             int inputSize = batchSize;
273             if (!Float.isNaN(validationRatio)) {
274                 inputSize = (int) (batchSize * (1 - validationRatio));
275             }
276             try {
277                 sketch.setConfig(batchSize, capacity);
278             } catch (IllegalArgumentException e) {
279                 // invalid parameter, ignore the config.
280                 Log.w(TAG, "Invalid parameter to heavy hitter watcher: "
281                         + batchSize + ", " + capacity);
282                 return;
283             }
284             // Reset the watcher to start over with the new configuration.
285             resetInternalLocked(listener, sketch, inputSize, batchSize, threshold, capacity);
286         }
287     }
288 
289     @GuardedBy("mLock")
resetInternalLocked(@ullable final BinderCallHeavyHitterListener listener, @Nullable final HeavyHitterSketch<Integer> sketch, final int inputSize, final int batchSize, final float threshold, final int capacity)290     private void resetInternalLocked(@Nullable final BinderCallHeavyHitterListener listener,
291             @Nullable final HeavyHitterSketch<Integer> sketch, final int inputSize,
292             final int batchSize, final float threshold, final int capacity) {
293         mListener = listener;
294         mHeavyHitterSketch = sketch;
295         mHeavyHitterCandiates.clear();
296         mCachedCandidateList.clear();
297         mCachedCandidateFrequencies.clear();
298         mCachedCandidateSet.clear();
299         mInputSize = inputSize;
300         mTotalInputSize = batchSize;
301         mCurrentInputSize = 0;
302         mThreshold = threshold;
303         mBatchStartTimeStamp = SystemClock.elapsedRealtime();
304         initCachedCandidateContainersLocked(capacity);
305     }
306 
307     @GuardedBy("mLock")
initCachedCandidateContainersLocked(final int capacity)308     private void initCachedCandidateContainersLocked(final int capacity) {
309         if (capacity > 0) {
310             mCachedCandidateContainers = new HeavyHitterContainer[capacity];
311             for (int i = 0; i < mCachedCandidateContainers.length; i++) {
312                 mCachedCandidateContainers[i] = new HeavyHitterContainer();
313             }
314         } else {
315             mCachedCandidateContainers = null;
316         }
317         mCachedCandidateContainersIndex = 0;
318     }
319 
320     @GuardedBy("mLock")
acquireHeavyHitterContainerLocked()321     private @NonNull HeavyHitterContainer acquireHeavyHitterContainerLocked() {
322         return mCachedCandidateContainers[mCachedCandidateContainersIndex++];
323     }
324 
325     @GuardedBy("mLock")
releaseHeavyHitterContainerLocked(@onNull HeavyHitterContainer container)326     private void releaseHeavyHitterContainerLocked(@NonNull HeavyHitterContainer container) {
327         mCachedCandidateContainers[--mCachedCandidateContainersIndex] = container;
328     }
329 
330     /**
331      * Called on incoming binder transaction
332      *
333      * @param callerUid The UID of the binder transaction's caller
334      * @param clazz     The class of the Binder object serving the transaction
335      * @param code      The binder transaction code
336      */
onTransaction(final int callerUid, @NonNull final Class clazz, final int code)337     public void onTransaction(final int callerUid, @NonNull final Class clazz,
338             final int code) {
339         synchronized (mLock) {
340             if (!mEnabled) {
341                 return;
342             }
343 
344             final HeavyHitterSketch<Integer> sketch = mHeavyHitterSketch;
345             if (sketch == null) {
346                 return;
347             }
348 
349             // To reduce memory fragmentation, we only feed the hashcode to the sketch,
350             // and keep the mapping from the hashcode to the sketch locally.
351             // However, the mapping will not be built until the validation pass, by then
352             // we will know the potential heavy hitters, so the mapping can focus on
353             // those ones, which will significantly reduce the memory overhead.
354             final int hashCode = HeavyHitterContainer.hashCode(callerUid, clazz, code);
355 
356             sketch.add(hashCode);
357             mCurrentInputSize++;
358             if (mCurrentInputSize == mInputSize) {
359                 // Retrieve the candidates
360                 sketch.getCandidates(mCachedCandidateList);
361                 mCachedCandidateSet.addAll(mCachedCandidateList);
362                 mCachedCandidateList.clear();
363             } else if (mCurrentInputSize > mInputSize && mCurrentInputSize < mTotalInputSize) {
364                 // validation pass
365                 if (mCachedCandidateSet.contains(hashCode)) {
366                     // It's one of the candidates
367                     final int index = mHeavyHitterCandiates.indexOfKey(hashCode);
368                     if (index < 0) {
369                         // We got another hit, now write down its information
370                         final HeavyHitterContainer container =
371                                 acquireHeavyHitterContainerLocked();
372                         container.mUid = callerUid;
373                         container.mClass = clazz;
374                         container.mCode = code;
375                         mHeavyHitterCandiates.put(hashCode, container);
376                     }
377                 }
378             } else if (mCurrentInputSize == mTotalInputSize) {
379                 // Reached the expected number of input, check top ones
380                 if (mListener != null) {
381                     final List<Integer> result = sketch.getTopHeavyHitters(0,
382                             mCachedCandidateList, mCachedCandidateFrequencies);
383                     if (result != null) {
384                         final int size = result.size();
385                         if (size > 0) {
386                             final ArrayList<HeavyHitterContainer> hitters = new ArrayList<>();
387                             for (int i = 0; i < size; i++) {
388                                 final HeavyHitterContainer container = mHeavyHitterCandiates.get(
389                                         result.get(i));
390                                 if (container != null) {
391                                     final HeavyHitterContainer cont =
392                                             new HeavyHitterContainer(container);
393                                     cont.mFrequency = mCachedCandidateFrequencies.get(i);
394                                     hitters.add(cont);
395                                 }
396                             }
397                             mListener.onHeavyHit(hitters, mTotalInputSize, mThreshold,
398                                     SystemClock.elapsedRealtime() - mBatchStartTimeStamp);
399                         }
400                     }
401                 }
402                 // reset
403                 mHeavyHitterSketch.reset();
404                 mHeavyHitterCandiates.clear();
405                 mCachedCandidateList.clear();
406                 mCachedCandidateFrequencies.clear();
407                 mCachedCandidateSet.clear();
408                 mCachedCandidateContainersIndex = 0;
409                 mCurrentInputSize = 0;
410                 mBatchStartTimeStamp = SystemClock.elapsedRealtime();
411             }
412         }
413     }
414 }
415