NetworkStatsHistory.java revision 75279904202357565cf5a1cb11148d01f42b4569
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.util.Arrays; 28 29/** 30 * Collection of historical network statistics, recorded into equally-sized 31 * "buckets" in time. Internally it stores data in {@code long} series for more 32 * efficient persistence. 33 * <p> 34 * Each bucket is defined by a {@link #bucketStart} timestamp, and lasts for 35 * {@link #bucketDuration}. Internally assumes that {@link #bucketStart} is 36 * sorted at all times. 37 * 38 * @hide 39 */ 40public class NetworkStatsHistory implements Parcelable { 41 private static final int VERSION = 1; 42 43 /** {@link #uid} value when UID details unavailable. */ 44 public static final int UID_ALL = -1; 45 46 // TODO: teach about zigzag encoding to use less disk space 47 // TODO: teach how to convert between bucket sizes 48 49 public final int networkType; 50 public final String identity; 51 public final int uid; 52 public final long bucketDuration; 53 54 int bucketCount; 55 long[] bucketStart; 56 long[] rx; 57 long[] tx; 58 59 public NetworkStatsHistory(int networkType, String identity, int uid, long bucketDuration) { 60 this.networkType = networkType; 61 this.identity = identity; 62 this.uid = uid; 63 this.bucketDuration = bucketDuration; 64 bucketStart = new long[0]; 65 rx = new long[0]; 66 tx = new long[0]; 67 bucketCount = bucketStart.length; 68 } 69 70 public NetworkStatsHistory(Parcel in) { 71 networkType = in.readInt(); 72 identity = in.readString(); 73 uid = in.readInt(); 74 bucketDuration = in.readLong(); 75 bucketStart = readLongArray(in); 76 rx = in.createLongArray(); 77 tx = in.createLongArray(); 78 bucketCount = bucketStart.length; 79 } 80 81 /** {@inheritDoc} */ 82 public void writeToParcel(Parcel out, int flags) { 83 out.writeInt(networkType); 84 out.writeString(identity); 85 out.writeInt(uid); 86 out.writeLong(bucketDuration); 87 writeLongArray(out, bucketStart, bucketCount); 88 writeLongArray(out, rx, bucketCount); 89 writeLongArray(out, tx, bucketCount); 90 } 91 92 public NetworkStatsHistory(DataInputStream in) throws IOException { 93 final int version = in.readInt(); 94 networkType = in.readInt(); 95 identity = in.readUTF(); 96 uid = in.readInt(); 97 bucketDuration = in.readLong(); 98 bucketStart = readLongArray(in); 99 rx = readLongArray(in); 100 tx = readLongArray(in); 101 bucketCount = bucketStart.length; 102 } 103 104 public void writeToStream(DataOutputStream out) throws IOException { 105 out.writeInt(VERSION); 106 out.writeInt(networkType); 107 out.writeUTF(identity); 108 out.writeInt(uid); 109 out.writeLong(bucketDuration); 110 writeLongArray(out, bucketStart, bucketCount); 111 writeLongArray(out, rx, bucketCount); 112 writeLongArray(out, tx, bucketCount); 113 } 114 115 /** {@inheritDoc} */ 116 public int describeContents() { 117 return 0; 118 } 119 120 /** 121 * Record that data traffic occurred in the given time range. Will 122 * distribute across internal buckets, creating new buckets as needed. 123 */ 124 public void recordData(long start, long end, long rx, long tx) { 125 // create any buckets needed by this range 126 ensureBuckets(start, end); 127 128 // distribute data usage into buckets 129 final long duration = end - start; 130 for (int i = bucketCount - 1; i >= 0; i--) { 131 final long curStart = bucketStart[i]; 132 final long curEnd = curStart + bucketDuration; 133 134 // bucket is older than record; we're finished 135 if (curEnd < start) break; 136 // bucket is newer than record; keep looking 137 if (curStart > end) continue; 138 139 final long overlap = Math.min(curEnd, end) - Math.max(curStart, start); 140 if (overlap > 0) { 141 this.rx[i] += rx * overlap / duration; 142 this.tx[i] += tx * overlap / duration; 143 } 144 } 145 } 146 147 /** 148 * Ensure that buckets exist for given time range, creating as needed. 149 */ 150 private void ensureBuckets(long start, long end) { 151 // normalize incoming range to bucket boundaries 152 start -= start % bucketDuration; 153 end += (bucketDuration - (end % bucketDuration)) % bucketDuration; 154 155 for (long now = start; now < end; now += bucketDuration) { 156 // try finding existing bucket 157 final int index = Arrays.binarySearch(bucketStart, 0, bucketCount, now); 158 if (index < 0) { 159 // bucket missing, create and insert 160 insertBucket(~index, now); 161 } 162 } 163 } 164 165 /** 166 * Insert new bucket at requested index and starting time. 167 */ 168 private void insertBucket(int index, long start) { 169 // create more buckets when needed 170 if (bucketCount + 1 > bucketStart.length) { 171 final int newLength = bucketStart.length + 10; 172 bucketStart = Arrays.copyOf(bucketStart, newLength); 173 rx = Arrays.copyOf(rx, newLength); 174 tx = Arrays.copyOf(tx, newLength); 175 } 176 177 // create gap when inserting bucket in middle 178 if (index < bucketCount) { 179 final int dstPos = index + 1; 180 final int length = bucketCount - index; 181 182 System.arraycopy(bucketStart, index, bucketStart, dstPos, length); 183 System.arraycopy(rx, index, rx, dstPos, length); 184 System.arraycopy(tx, index, tx, dstPos, length); 185 } 186 187 bucketStart[index] = start; 188 rx[index] = 0; 189 tx[index] = 0; 190 bucketCount++; 191 } 192 193 /** 194 * Remove buckets older than requested cutoff. 195 */ 196 public void removeBucketsBefore(long cutoff) { 197 int i; 198 for (i = 0; i < bucketCount; i++) { 199 final long curStart = bucketStart[i]; 200 final long curEnd = curStart + bucketDuration; 201 202 // cutoff happens before or during this bucket; everything before 203 // this bucket should be removed. 204 if (curEnd > cutoff) break; 205 } 206 207 if (i > 0) { 208 final int length = bucketStart.length; 209 bucketStart = Arrays.copyOfRange(bucketStart, i, length); 210 rx = Arrays.copyOfRange(rx, i, length); 211 tx = Arrays.copyOfRange(tx, i, length); 212 bucketCount -= i; 213 } 214 } 215 216 public void dump(String prefix, PrintWriter pw) { 217 // TODO: consider stripping identity when dumping 218 pw.print(prefix); 219 pw.print("NetworkStatsHistory: networkType="); pw.print(networkType); 220 pw.print(" identity="); pw.print(identity); 221 pw.print(" uid="); pw.println(uid); 222 for (int i = 0; i < bucketCount; i++) { 223 pw.print(prefix); 224 pw.print(" timestamp="); pw.print(bucketStart[i]); 225 pw.print(" rx="); pw.print(rx[i]); 226 pw.print(" tx="); pw.println(tx[i]); 227 } 228 } 229 230 @Override 231 public String toString() { 232 final CharArrayWriter writer = new CharArrayWriter(); 233 dump("", new PrintWriter(writer)); 234 return writer.toString(); 235 } 236 237 public static final Creator<NetworkStatsHistory> CREATOR = new Creator<NetworkStatsHistory>() { 238 public NetworkStatsHistory createFromParcel(Parcel in) { 239 return new NetworkStatsHistory(in); 240 } 241 242 public NetworkStatsHistory[] newArray(int size) { 243 return new NetworkStatsHistory[size]; 244 } 245 }; 246 247 private static long[] readLongArray(DataInputStream in) throws IOException { 248 final int size = in.readInt(); 249 final long[] values = new long[size]; 250 for (int i = 0; i < values.length; i++) { 251 values[i] = in.readLong(); 252 } 253 return values; 254 } 255 256 private static void writeLongArray(DataOutputStream out, long[] values, int size) throws IOException { 257 if (size > values.length) { 258 throw new IllegalArgumentException("size larger than length"); 259 } 260 out.writeInt(size); 261 for (int i = 0; i < size; i++) { 262 out.writeLong(values[i]); 263 } 264 } 265 266 private static long[] readLongArray(Parcel in) { 267 final int size = in.readInt(); 268 final long[] values = new long[size]; 269 for (int i = 0; i < values.length; i++) { 270 values[i] = in.readLong(); 271 } 272 return values; 273 } 274 275 private static void writeLongArray(Parcel out, long[] values, int size) { 276 if (size > values.length) { 277 throw new IllegalArgumentException("size larger than length"); 278 } 279 out.writeInt(size); 280 for (int i = 0; i < size; i++) { 281 out.writeLong(values[i]); 282 } 283 } 284 285} 286