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