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