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.SystemApi; 19 import android.os.Parcel; 20 import android.os.Parcelable; 21 22 import java.util.ArrayList; 23 import java.util.Arrays; 24 import java.util.List; 25 26 /** 27 * Parcelable class to store Telephony histogram. 28 * @hide 29 */ 30 @SystemApi 31 public 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