NetworkStatsHistory.java revision 350083e36b9db6062e165954403ef921ff3dfdad
1/* 2 * Copyright (C) 2011 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 17package android.net; 18 19import android.os.Parcel; 20import android.os.Parcelable; 21 22import java.io.CharArrayWriter; 23import java.io.DataInputStream; 24import java.io.DataOutputStream; 25import java.io.IOException; 26import java.io.PrintWriter; 27import java.net.ProtocolException; 28import java.util.Arrays; 29import java.util.Random; 30 31/** 32 * Collection of historical network statistics, recorded into equally-sized 33 * "buckets" in time. Internally it stores data in {@code long} series for more 34 * efficient persistence. 35 * <p> 36 * Each bucket is defined by a {@link #bucketStart} timestamp, and lasts for 37 * {@link #bucketDuration}. Internally assumes that {@link #bucketStart} is 38 * sorted at all times. 39 * 40 * @hide 41 */ 42public class NetworkStatsHistory implements Parcelable { 43 private static final int VERSION_INIT = 1; 44 45 // TODO: teach about varint encoding to use less disk space 46 47 public final long bucketDuration; 48 49 public int bucketCount; 50 public long[] bucketStart; 51 public long[] rx; 52 public long[] tx; 53 54 public NetworkStatsHistory(long bucketDuration) { 55 this(bucketDuration, 10); 56 } 57 58 public NetworkStatsHistory(long bucketDuration, int initialSize) { 59 this.bucketDuration = bucketDuration; 60 bucketStart = new long[initialSize]; 61 rx = new long[initialSize]; 62 tx = new long[initialSize]; 63 bucketCount = 0; 64 } 65 66 public NetworkStatsHistory(Parcel in) { 67 bucketDuration = in.readLong(); 68 bucketStart = readLongArray(in); 69 rx = in.createLongArray(); 70 tx = in.createLongArray(); 71 bucketCount = bucketStart.length; 72 } 73 74 /** {@inheritDoc} */ 75 public void writeToParcel(Parcel out, int flags) { 76 out.writeLong(bucketDuration); 77 writeLongArray(out, bucketStart, bucketCount); 78 writeLongArray(out, rx, bucketCount); 79 writeLongArray(out, tx, bucketCount); 80 } 81 82 public NetworkStatsHistory(DataInputStream in) throws IOException { 83 final int version = in.readInt(); 84 switch (version) { 85 case VERSION_INIT: { 86 bucketDuration = in.readLong(); 87 bucketStart = readLongArray(in); 88 rx = readLongArray(in); 89 tx = readLongArray(in); 90 bucketCount = bucketStart.length; 91 break; 92 } 93 default: { 94 throw new ProtocolException("unexpected version: " + version); 95 } 96 } 97 } 98 99 public void writeToStream(DataOutputStream out) throws IOException { 100 out.writeInt(VERSION_INIT); 101 out.writeLong(bucketDuration); 102 writeLongArray(out, bucketStart, bucketCount); 103 writeLongArray(out, rx, bucketCount); 104 writeLongArray(out, tx, bucketCount); 105 } 106 107 /** {@inheritDoc} */ 108 public int describeContents() { 109 return 0; 110 } 111 112 /** 113 * Record that data traffic occurred in the given time range. Will 114 * distribute across internal buckets, creating new buckets as needed. 115 */ 116 public void recordData(long start, long end, long rx, long tx) { 117 if (rx < 0 || tx < 0) { 118 throw new IllegalArgumentException( 119 "tried recording negative data: rx=" + rx + ", tx=" + tx); 120 } 121 122 // create any buckets needed by this range 123 ensureBuckets(start, end); 124 125 // distribute data usage into buckets 126 final long duration = end - start; 127 for (int i = bucketCount - 1; i >= 0; i--) { 128 final long curStart = bucketStart[i]; 129 final long curEnd = curStart + bucketDuration; 130 131 // bucket is older than record; we're finished 132 if (curEnd < start) break; 133 // bucket is newer than record; keep looking 134 if (curStart > end) continue; 135 136 final long overlap = Math.min(curEnd, end) - Math.max(curStart, start); 137 if (overlap > 0) { 138 this.rx[i] += rx * overlap / duration; 139 this.tx[i] += tx * overlap / duration; 140 } 141 } 142 } 143 144 /** 145 * Record an entire {@link NetworkStatsHistory} into this history. Usually 146 * for combining together stats for external reporting. 147 */ 148 public void recordEntireHistory(NetworkStatsHistory input) { 149 for (int i = 0; i < input.bucketCount; i++) { 150 final long start = input.bucketStart[i]; 151 final long end = start + input.bucketDuration; 152 recordData(start, end, input.rx[i], input.tx[i]); 153 } 154 } 155 156 /** 157 * Ensure that buckets exist for given time range, creating as needed. 158 */ 159 private void ensureBuckets(long start, long end) { 160 // normalize incoming range to bucket boundaries 161 start -= start % bucketDuration; 162 end += (bucketDuration - (end % bucketDuration)) % bucketDuration; 163 164 for (long now = start; now < end; now += bucketDuration) { 165 // try finding existing bucket 166 final int index = Arrays.binarySearch(bucketStart, 0, bucketCount, now); 167 if (index < 0) { 168 // bucket missing, create and insert 169 insertBucket(~index, now); 170 } 171 } 172 } 173 174 /** 175 * Insert new bucket at requested index and starting time. 176 */ 177 private void insertBucket(int index, long start) { 178 // create more buckets when needed 179 if (bucketCount >= bucketStart.length) { 180 final int newLength = Math.max(bucketStart.length, 10) * 3 / 2; 181 bucketStart = Arrays.copyOf(bucketStart, newLength); 182 rx = Arrays.copyOf(rx, newLength); 183 tx = Arrays.copyOf(tx, newLength); 184 } 185 186 // create gap when inserting bucket in middle 187 if (index < bucketCount) { 188 final int dstPos = index + 1; 189 final int length = bucketCount - index; 190 191 System.arraycopy(bucketStart, index, bucketStart, dstPos, length); 192 System.arraycopy(rx, index, rx, dstPos, length); 193 System.arraycopy(tx, index, tx, dstPos, length); 194 } 195 196 bucketStart[index] = start; 197 rx[index] = 0; 198 tx[index] = 0; 199 bucketCount++; 200 } 201 202 /** 203 * Remove buckets older than requested cutoff. 204 */ 205 public void removeBucketsBefore(long cutoff) { 206 int i; 207 for (i = 0; i < bucketCount; i++) { 208 final long curStart = bucketStart[i]; 209 final long curEnd = curStart + bucketDuration; 210 211 // cutoff happens before or during this bucket; everything before 212 // this bucket should be removed. 213 if (curEnd > cutoff) break; 214 } 215 216 if (i > 0) { 217 final int length = bucketStart.length; 218 bucketStart = Arrays.copyOfRange(bucketStart, i, length); 219 rx = Arrays.copyOfRange(rx, i, length); 220 tx = Arrays.copyOfRange(tx, i, length); 221 bucketCount -= i; 222 } 223 } 224 225 /** 226 * Return interpolated data usage across the requested range. Interpolates 227 * across buckets, so values may be rounded slightly. 228 */ 229 public long[] getTotalData(long start, long end, long[] outTotal) { 230 long rx = 0; 231 long tx = 0; 232 233 for (int i = bucketCount - 1; i >= 0; i--) { 234 final long curStart = bucketStart[i]; 235 final long curEnd = curStart + bucketDuration; 236 237 // bucket is older than record; we're finished 238 if (curEnd < start) break; 239 // bucket is newer than record; keep looking 240 if (curStart > end) continue; 241 242 final long overlap = Math.min(curEnd, end) - Math.max(curStart, start); 243 if (overlap > 0) { 244 rx += this.rx[i] * overlap / bucketDuration; 245 tx += this.tx[i] * overlap / bucketDuration; 246 } 247 } 248 249 if (outTotal == null || outTotal.length != 2) { 250 outTotal = new long[2]; 251 } 252 outTotal[0] = rx; 253 outTotal[1] = tx; 254 return outTotal; 255 } 256 257 /** 258 * @deprecated only for temporary testing 259 */ 260 @Deprecated 261 public void generateRandom(long start, long end, long rx, long tx) { 262 ensureBuckets(start, end); 263 264 final Random r = new Random(); 265 while (rx > 1024 && tx > 1024) { 266 final long curStart = randomLong(r, start, end); 267 final long curEnd = randomLong(r, curStart, end); 268 final long curRx = randomLong(r, 0, rx); 269 final long curTx = randomLong(r, 0, tx); 270 271 recordData(curStart, curEnd, curRx, curTx); 272 273 rx -= curRx; 274 tx -= curTx; 275 } 276 } 277 278 private static long randomLong(Random r, long start, long end) { 279 return (long) (start + (r.nextFloat() * (end - start))); 280 } 281 282 public void dump(String prefix, PrintWriter pw, boolean fullHistory) { 283 pw.print(prefix); 284 pw.print("NetworkStatsHistory: bucketDuration="); pw.println(bucketDuration); 285 286 final int start = fullHistory ? 0 : Math.max(0, bucketCount - 32); 287 if (start > 0) { 288 pw.print(prefix); 289 pw.print(" (omitting "); pw.print(start); pw.println(" buckets)"); 290 } 291 292 for (int i = start; i < bucketCount; i++) { 293 pw.print(prefix); 294 pw.print(" bucketStart="); pw.print(bucketStart[i]); 295 pw.print(" rx="); pw.print(rx[i]); 296 pw.print(" tx="); pw.println(tx[i]); 297 } 298 } 299 300 @Override 301 public String toString() { 302 final CharArrayWriter writer = new CharArrayWriter(); 303 dump("", new PrintWriter(writer), false); 304 return writer.toString(); 305 } 306 307 public static final Creator<NetworkStatsHistory> CREATOR = new Creator<NetworkStatsHistory>() { 308 public NetworkStatsHistory createFromParcel(Parcel in) { 309 return new NetworkStatsHistory(in); 310 } 311 312 public NetworkStatsHistory[] newArray(int size) { 313 return new NetworkStatsHistory[size]; 314 } 315 }; 316 317 private static long[] readLongArray(DataInputStream in) throws IOException { 318 final int size = in.readInt(); 319 final long[] values = new long[size]; 320 for (int i = 0; i < values.length; i++) { 321 values[i] = in.readLong(); 322 } 323 return values; 324 } 325 326 private static void writeLongArray(DataOutputStream out, long[] values, int size) throws IOException { 327 if (size > values.length) { 328 throw new IllegalArgumentException("size larger than length"); 329 } 330 out.writeInt(size); 331 for (int i = 0; i < size; i++) { 332 out.writeLong(values[i]); 333 } 334 } 335 336 private static long[] readLongArray(Parcel in) { 337 final int size = in.readInt(); 338 final long[] values = new long[size]; 339 for (int i = 0; i < values.length; i++) { 340 values[i] = in.readLong(); 341 } 342 return values; 343 } 344 345 private static void writeLongArray(Parcel out, long[] values, int size) { 346 if (size > values.length) { 347 throw new IllegalArgumentException("size larger than length"); 348 } 349 out.writeInt(size); 350 for (int i = 0; i < size; i++) { 351 out.writeLong(values[i]); 352 } 353 } 354 355} 356