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