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