1 /*
2  * Copyright (C) 2016 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 package android.telephony;
17 
18 import android.annotation.NonNull;
19 import android.annotation.SystemApi;
20 import android.os.Parcel;
21 import android.os.Parcelable;
22 
23 import java.util.Arrays;
24 
25 /**
26  * Parcelable class to store Telephony histogram.
27  * @hide
28  */
29 @SystemApi
30 public final class TelephonyHistogram implements Parcelable {
31     // Type of Telephony histogram Eg: RIL histogram will have all timing data associated with
32     // RIL calls. Similarly we can have any other Telephony histogram.
33     private final int mCategory;
34 
35     // Unique Id identifying a sample within particular category of histogram
36     private final int mId;
37 
38     // Min time taken in ms
39     private int mMinTimeMs;
40 
41     // Max time taken in ms
42     private int mMaxTimeMs;
43 
44     // Average time taken in ms
45     private int mAverageTimeMs;
46 
47     // Total count of samples
48     private int mSampleCount;
49 
50     // Array storing time taken for first #RANGE_CALCULATION_COUNT samples of histogram.
51     private int[] mInitialTimings;
52 
53     // Total number of time ranges expected (must be greater than 1)
54     private final int mBucketCount;
55 
56     // Array storing endpoints of range buckets. Calculated based on values of minTime & maxTime
57     // after totalTimeCount is #RANGE_CALCULATION_COUNT.
58     private final int[] mBucketEndPoints;
59 
60     // Array storing counts for each time range starting from smallest value range
61     private final int[] mBucketCounters;
62 
63     /**
64      * Constant for Telephony category
65      */
66     public static final int TELEPHONY_CATEGORY_RIL = 1;
67 
68     // Count of Histogram samples after which time buckets are created.
69     private static final int RANGE_CALCULATION_COUNT = 10;
70 
71 
72     // Constant used to indicate #initialTimings is null while parceling
73     private static final int ABSENT = 0;
74 
75     // Constant used to indicate #initialTimings is not null while parceling
76     private static final int PRESENT = 1;
77 
78     // Throws exception if #totalBuckets is not greater than one.
TelephonyHistogram(int category, int id, int bucketCount)79     public TelephonyHistogram (int category, int id, int bucketCount) {
80         if (bucketCount <= 1) {
81             throw new IllegalArgumentException("Invalid number of buckets");
82         }
83         mCategory = category;
84         mId = id;
85         mMinTimeMs = Integer.MAX_VALUE;
86         mMaxTimeMs = 0;
87         mAverageTimeMs = 0;
88         mSampleCount = 0;
89         mInitialTimings = new int[RANGE_CALCULATION_COUNT];
90         mBucketCount = bucketCount;
91         mBucketEndPoints = new int[bucketCount - 1];
92         mBucketCounters = new int[bucketCount];
93     }
94 
TelephonyHistogram(TelephonyHistogram th)95     public TelephonyHistogram(TelephonyHistogram th) {
96         mCategory = th.getCategory();
97         mId = th.getId();
98         mMinTimeMs = th.getMinTime();
99         mMaxTimeMs = th.getMaxTime();
100         mAverageTimeMs = th.getAverageTime();
101         mSampleCount = th.getSampleCount();
102         mInitialTimings = th.getInitialTimings();
103         mBucketCount = th.getBucketCount();
104         mBucketEndPoints = th.getBucketEndPoints();
105         mBucketCounters = th.getBucketCounters();
106     }
107 
getCategory()108     public int getCategory() {
109         return mCategory;
110     }
111 
getId()112     public int getId() {
113         return mId;
114     }
115 
getMinTime()116     public int getMinTime() {
117         return mMinTimeMs;
118     }
119 
getMaxTime()120     public int getMaxTime() {
121         return mMaxTimeMs;
122     }
123 
getAverageTime()124     public int getAverageTime() {
125         return mAverageTimeMs;
126     }
127 
getSampleCount()128     public int getSampleCount () {
129         return mSampleCount;
130     }
131 
getInitialTimings()132     private int[] getInitialTimings() {
133         return mInitialTimings;
134     }
135 
getBucketCount()136     public int getBucketCount() {
137         return mBucketCount;
138     }
139 
getBucketEndPoints()140     public int[] getBucketEndPoints() {
141         if (mSampleCount > 1 && mSampleCount < 10) {
142             int[] tempEndPoints = new int[mBucketCount - 1];
143             calculateBucketEndPoints(tempEndPoints);
144             return tempEndPoints;
145         } else {
146             return getDeepCopyOfArray(mBucketEndPoints);
147         }
148     }
149 
getBucketCounters()150     public int[] getBucketCounters() {
151         if (mSampleCount > 1 && mSampleCount < 10) {
152             int[] tempEndPoints = new int[mBucketCount - 1];
153             int[] tempBucketCounters = new int[mBucketCount];
154             calculateBucketEndPoints(tempEndPoints);
155             for (int j = 0; j < mSampleCount; j++) {
156                 addToBucketCounter(tempEndPoints, tempBucketCounters, mInitialTimings[j]);
157             }
158             return tempBucketCounters;
159         } else {
160             return getDeepCopyOfArray(mBucketCounters);
161         }
162     }
163 
getDeepCopyOfArray(int[] array)164     private int[] getDeepCopyOfArray(int[] array) {
165         int[] clone = new int[array.length];
166         System.arraycopy(array, 0, clone, 0, array.length);
167         return clone;
168     }
169 
addToBucketCounter(int[] bucketEndPoints, int[] bucketCounters, int time)170     private void addToBucketCounter(int[] bucketEndPoints, int[] bucketCounters, int time) {
171         int i;
172         for (i = 0; i < bucketEndPoints.length; i++) {
173             if (time <= bucketEndPoints[i]) {
174                 bucketCounters[i]++;
175                 return;
176             }
177         }
178         bucketCounters[i]++;
179     }
180 
calculateBucketEndPoints(int[] bucketEndPoints)181     private void calculateBucketEndPoints(int[] bucketEndPoints) {
182         for (int i = 1; i < mBucketCount; i++) {
183             int endPt = mMinTimeMs + (i * (mMaxTimeMs - mMinTimeMs)) / mBucketCount;
184             bucketEndPoints[i - 1] = endPt;
185         }
186     }
187 
188     // Add new value of time taken
189     // This function updates minTime, maxTime, averageTime & totalTimeCount every time it is
190     // called. initialTimings[] is updated if totalTimeCount <= #RANGE_CALCULATION_COUNT. When
191     // totalTimeCount = RANGE_CALCULATION_COUNT, based on the min, max time & the number of buckets
192     // expected, bucketEndPoints[] would be calculated. Then bucketCounters[] would be filled up
193     // using values stored in initialTimings[]. Thereafter bucketCounters[] will always be updated.
addTimeTaken(int time)194     public void addTimeTaken(int time) {
195         // Initialize all fields if its first entry or if integer overflow is going to occur while
196         // trying to calculate averageTime
197         if (mSampleCount == 0 || (mSampleCount == Integer.MAX_VALUE)) {
198             if (mSampleCount == 0) {
199                 mMinTimeMs = time;
200                 mMaxTimeMs = time;
201                 mAverageTimeMs = time;
202             } else {
203                 mInitialTimings = new int[RANGE_CALCULATION_COUNT];
204             }
205             mSampleCount = 1;
206             Arrays.fill(mInitialTimings, 0);
207             mInitialTimings[0] = time;
208             Arrays.fill(mBucketEndPoints, 0);
209             Arrays.fill(mBucketCounters, 0);
210         } else {
211             if (time < mMinTimeMs) {
212                 mMinTimeMs = time;
213             }
214             if (time > mMaxTimeMs) {
215                 mMaxTimeMs = time;
216             }
217             long totalTime = ((long)mAverageTimeMs) * mSampleCount + time;
218             mAverageTimeMs = (int)(totalTime/++mSampleCount);
219 
220             if (mSampleCount < RANGE_CALCULATION_COUNT) {
221                 mInitialTimings[mSampleCount - 1] = time;
222             } else if (mSampleCount == RANGE_CALCULATION_COUNT) {
223                 mInitialTimings[mSampleCount - 1] = time;
224 
225                 // Calculate bucket endpoints based on bucketCount expected
226                 calculateBucketEndPoints(mBucketEndPoints);
227 
228                 // Use values stored in initialTimings[] to update bucketCounters
229                 for (int j = 0; j < RANGE_CALCULATION_COUNT; j++) {
230                     addToBucketCounter(mBucketEndPoints, mBucketCounters, mInitialTimings[j]);
231                 }
232                 mInitialTimings = null;
233             } else {
234                 addToBucketCounter(mBucketEndPoints, mBucketCounters, time);
235             }
236 
237         }
238     }
239 
240     @NonNull
241     @Override
242     public String toString() {
243         String basic = " Histogram id = " + mId + " Time(ms): min = " + mMinTimeMs + " max = "
244                 + mMaxTimeMs + " avg = " + mAverageTimeMs + " Count = " + mSampleCount;
245         if (mSampleCount < RANGE_CALCULATION_COUNT) {
246             return basic;
247         } else {
248             StringBuffer intervals = new StringBuffer(" Interval Endpoints:");
249             for (int i = 0; i < mBucketEndPoints.length; i++) {
250                 intervals.append(" " + mBucketEndPoints[i]);
251             }
252             intervals.append(" Interval counters:");
253             for (int i = 0; i < mBucketCounters.length; i++) {
254                 intervals.append(" " + mBucketCounters[i]);
255             }
256             return basic + intervals;
257         }
258     }
259 
260     public static final @android.annotation.NonNull Parcelable.Creator<TelephonyHistogram> CREATOR =
261             new Parcelable.Creator<TelephonyHistogram> () {
262 
263                 @Override
264                 public TelephonyHistogram createFromParcel(Parcel in) {
265                     return new TelephonyHistogram(in);
266                 }
267 
268                 @Override
269                 public TelephonyHistogram[] newArray(int size) {
270                     return new TelephonyHistogram[size];
271                 }
272             };
273 
274     public TelephonyHistogram(Parcel in) {
275         mCategory = in.readInt();
276         mId = in.readInt();
277         mMinTimeMs = in.readInt();
278         mMaxTimeMs = in.readInt();
279         mAverageTimeMs = in.readInt();
280         mSampleCount = in.readInt();
281         if (in.readInt() == PRESENT) {
282             mInitialTimings = new int[RANGE_CALCULATION_COUNT];
283             in.readIntArray(mInitialTimings);
284         }
285         mBucketCount = in.readInt();
286         mBucketEndPoints = new int[mBucketCount - 1];
287         in.readIntArray(mBucketEndPoints);
288         mBucketCounters = new int[mBucketCount];
289         in.readIntArray(mBucketCounters);
290     }
291 
292     public void writeToParcel(Parcel out, int flags) {
293         out.writeInt(mCategory);
294         out.writeInt(mId);
295         out.writeInt(mMinTimeMs);
296         out.writeInt(mMaxTimeMs);
297         out.writeInt(mAverageTimeMs);
298         out.writeInt(mSampleCount);
299         if (mInitialTimings == null) {
300             out.writeInt(ABSENT);
301         } else {
302             out.writeInt(PRESENT);
303             out.writeIntArray(mInitialTimings);
304         }
305         out.writeInt(mBucketCount);
306         out.writeIntArray(mBucketEndPoints);
307         out.writeIntArray(mBucketCounters);
308     }
309 
310     @Override
311     public int describeContents() {
312         return 0;
313     }
314 }
315