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