NetworkStatsHistory.java revision b5d55e302d2253e4bfb233ea705caf258cdc4cb9
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 static android.net.NetworkStats.IFACE_ALL; 20import static android.net.NetworkStats.SET_DEFAULT; 21import static android.net.NetworkStats.TAG_NONE; 22import static android.net.NetworkStats.UID_ALL; 23import static android.net.NetworkStatsHistory.DataStreamUtils.readFullLongArray; 24import static android.net.NetworkStatsHistory.DataStreamUtils.readVarLongArray; 25import static android.net.NetworkStatsHistory.DataStreamUtils.writeVarLongArray; 26import static android.net.NetworkStatsHistory.Entry.UNKNOWN; 27import static android.net.NetworkStatsHistory.ParcelUtils.readLongArray; 28import static android.net.NetworkStatsHistory.ParcelUtils.writeLongArray; 29 30import android.os.Parcel; 31import android.os.Parcelable; 32 33import java.io.CharArrayWriter; 34import java.io.DataInputStream; 35import java.io.DataOutputStream; 36import java.io.IOException; 37import java.io.PrintWriter; 38import java.net.ProtocolException; 39import java.util.Arrays; 40import java.util.Random; 41 42/** 43 * Collection of historical network statistics, recorded into equally-sized 44 * "buckets" in time. Internally it stores data in {@code long} series for more 45 * efficient persistence. 46 * <p> 47 * Each bucket is defined by a {@link #bucketStart} timestamp, and lasts for 48 * {@link #bucketDuration}. Internally assumes that {@link #bucketStart} is 49 * sorted at all times. 50 * 51 * @hide 52 */ 53public class NetworkStatsHistory implements Parcelable { 54 private static final int VERSION_INIT = 1; 55 private static final int VERSION_ADD_PACKETS = 2; 56 57 public static final int FIELD_RX_BYTES = 0x01; 58 public static final int FIELD_RX_PACKETS = 0x02; 59 public static final int FIELD_TX_BYTES = 0x04; 60 public static final int FIELD_TX_PACKETS = 0x08; 61 public static final int FIELD_OPERATIONS = 0x10; 62 63 public static final int FIELD_ALL = 0xFFFFFFFF; 64 65 private long bucketDuration; 66 private int bucketCount; 67 private long[] bucketStart; 68 private long[] rxBytes; 69 private long[] rxPackets; 70 private long[] txBytes; 71 private long[] txPackets; 72 private long[] operations; 73 74 public static class Entry { 75 public static final long UNKNOWN = -1; 76 77 public long bucketStart; 78 public long bucketDuration; 79 public long rxBytes; 80 public long rxPackets; 81 public long txBytes; 82 public long txPackets; 83 public long operations; 84 } 85 86 public NetworkStatsHistory(long bucketDuration) { 87 this(bucketDuration, 10, FIELD_ALL); 88 } 89 90 public NetworkStatsHistory(long bucketDuration, int initialSize) { 91 this(bucketDuration, initialSize, FIELD_ALL); 92 } 93 94 public NetworkStatsHistory(long bucketDuration, int initialSize, int fields) { 95 this.bucketDuration = bucketDuration; 96 bucketStart = new long[initialSize]; 97 if ((fields & FIELD_RX_BYTES) != 0) rxBytes = new long[initialSize]; 98 if ((fields & FIELD_RX_PACKETS) != 0) rxPackets = new long[initialSize]; 99 if ((fields & FIELD_TX_BYTES) != 0) txBytes = new long[initialSize]; 100 if ((fields & FIELD_TX_PACKETS) != 0) txPackets = new long[initialSize]; 101 if ((fields & FIELD_OPERATIONS) != 0) operations = new long[initialSize]; 102 bucketCount = 0; 103 } 104 105 public NetworkStatsHistory(Parcel in) { 106 bucketDuration = in.readLong(); 107 bucketStart = readLongArray(in); 108 rxBytes = readLongArray(in); 109 rxPackets = readLongArray(in); 110 txBytes = readLongArray(in); 111 txPackets = readLongArray(in); 112 operations = readLongArray(in); 113 bucketCount = bucketStart.length; 114 } 115 116 /** {@inheritDoc} */ 117 public void writeToParcel(Parcel out, int flags) { 118 out.writeLong(bucketDuration); 119 writeLongArray(out, bucketStart, bucketCount); 120 writeLongArray(out, rxBytes, bucketCount); 121 writeLongArray(out, rxPackets, bucketCount); 122 writeLongArray(out, txBytes, bucketCount); 123 writeLongArray(out, txPackets, bucketCount); 124 writeLongArray(out, operations, bucketCount); 125 } 126 127 public NetworkStatsHistory(DataInputStream in) throws IOException { 128 final int version = in.readInt(); 129 switch (version) { 130 case VERSION_INIT: { 131 bucketDuration = in.readLong(); 132 bucketStart = readFullLongArray(in); 133 rxBytes = readFullLongArray(in); 134 rxPackets = new long[bucketStart.length]; 135 txBytes = readFullLongArray(in); 136 txPackets = new long[bucketStart.length]; 137 operations = new long[bucketStart.length]; 138 bucketCount = bucketStart.length; 139 break; 140 } 141 case VERSION_ADD_PACKETS: { 142 bucketDuration = in.readLong(); 143 bucketStart = readVarLongArray(in); 144 rxBytes = readVarLongArray(in); 145 rxPackets = readVarLongArray(in); 146 txBytes = readVarLongArray(in); 147 txPackets = readVarLongArray(in); 148 operations = readVarLongArray(in); 149 bucketCount = bucketStart.length; 150 break; 151 } 152 default: { 153 throw new ProtocolException("unexpected version: " + version); 154 } 155 } 156 } 157 158 public void writeToStream(DataOutputStream out) throws IOException { 159 out.writeInt(VERSION_ADD_PACKETS); 160 out.writeLong(bucketDuration); 161 writeVarLongArray(out, bucketStart, bucketCount); 162 writeVarLongArray(out, rxBytes, bucketCount); 163 writeVarLongArray(out, rxPackets, bucketCount); 164 writeVarLongArray(out, txBytes, bucketCount); 165 writeVarLongArray(out, txPackets, bucketCount); 166 writeVarLongArray(out, operations, bucketCount); 167 } 168 169 /** {@inheritDoc} */ 170 public int describeContents() { 171 return 0; 172 } 173 174 public int size() { 175 return bucketCount; 176 } 177 178 public long getBucketDuration() { 179 return bucketDuration; 180 } 181 182 public long getStart() { 183 if (bucketCount > 0) { 184 return bucketStart[0]; 185 } else { 186 return Long.MAX_VALUE; 187 } 188 } 189 190 public long getEnd() { 191 if (bucketCount > 0) { 192 return bucketStart[bucketCount - 1] + bucketDuration; 193 } else { 194 return Long.MIN_VALUE; 195 } 196 } 197 198 /** 199 * Return specific stats entry. 200 */ 201 public Entry getValues(int i, Entry recycle) { 202 final Entry entry = recycle != null ? recycle : new Entry(); 203 entry.bucketStart = bucketStart[i]; 204 entry.bucketDuration = bucketDuration; 205 entry.rxBytes = getLong(rxBytes, i, UNKNOWN); 206 entry.rxPackets = getLong(rxPackets, i, UNKNOWN); 207 entry.txBytes = getLong(txBytes, i, UNKNOWN); 208 entry.txPackets = getLong(txPackets, i, UNKNOWN); 209 entry.operations = getLong(operations, i, UNKNOWN); 210 return entry; 211 } 212 213 /** 214 * Record that data traffic occurred in the given time range. Will 215 * distribute across internal buckets, creating new buckets as needed. 216 */ 217 @Deprecated 218 public void recordData(long start, long end, long rxBytes, long txBytes) { 219 recordData(start, end, new NetworkStats.Entry( 220 IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, 0L, txBytes, 0L, 0L)); 221 } 222 223 /** 224 * Record that data traffic occurred in the given time range. Will 225 * distribute across internal buckets, creating new buckets as needed. 226 */ 227 public void recordData(long start, long end, NetworkStats.Entry entry) { 228 if (entry.rxBytes < 0 || entry.rxPackets < 0 || entry.txBytes < 0 || entry.txPackets < 0 229 || entry.operations < 0) { 230 throw new IllegalArgumentException("tried recording negative data"); 231 } 232 233 // create any buckets needed by this range 234 ensureBuckets(start, end); 235 236 // distribute data usage into buckets 237 long duration = end - start; 238 for (int i = bucketCount - 1; i >= 0; i--) { 239 final long curStart = bucketStart[i]; 240 final long curEnd = curStart + bucketDuration; 241 242 // bucket is older than record; we're finished 243 if (curEnd < start) break; 244 // bucket is newer than record; keep looking 245 if (curStart > end) continue; 246 247 final long overlap = Math.min(curEnd, end) - Math.max(curStart, start); 248 if (overlap <= 0) continue; 249 250 // integer math each time is faster than floating point 251 final long fracRxBytes = entry.rxBytes * overlap / duration; 252 final long fracRxPackets = entry.rxPackets * overlap / duration; 253 final long fracTxBytes = entry.txBytes * overlap / duration; 254 final long fracTxPackets = entry.txPackets * overlap / duration; 255 final int fracOperations = (int) (entry.operations * overlap / duration); 256 257 addLong(rxBytes, i, fracRxBytes); entry.rxBytes -= fracRxBytes; 258 addLong(rxPackets, i, fracRxPackets); entry.rxPackets -= fracRxPackets; 259 addLong(txBytes, i, fracTxBytes); entry.txBytes -= fracTxBytes; 260 addLong(txPackets, i, fracTxPackets); entry.txPackets -= fracTxPackets; 261 addLong(operations, i, fracOperations); entry.operations -= fracOperations; 262 263 duration -= overlap; 264 } 265 } 266 267 /** 268 * Record an entire {@link NetworkStatsHistory} into this history. Usually 269 * for combining together stats for external reporting. 270 */ 271 public void recordEntireHistory(NetworkStatsHistory input) { 272 final NetworkStats.Entry entry = new NetworkStats.Entry( 273 IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L); 274 for (int i = 0; i < input.bucketCount; i++) { 275 final long start = input.bucketStart[i]; 276 final long end = start + input.bucketDuration; 277 278 entry.rxBytes = getLong(input.rxBytes, i, 0L); 279 entry.rxPackets = getLong(input.rxPackets, i, 0L); 280 entry.txBytes = getLong(input.txBytes, i, 0L); 281 entry.txPackets = getLong(input.txPackets, i, 0L); 282 entry.operations = getLong(input.operations, i, 0L); 283 284 recordData(start, end, entry); 285 } 286 } 287 288 /** 289 * Ensure that buckets exist for given time range, creating as needed. 290 */ 291 private void ensureBuckets(long start, long end) { 292 // normalize incoming range to bucket boundaries 293 start -= start % bucketDuration; 294 end += (bucketDuration - (end % bucketDuration)) % bucketDuration; 295 296 for (long now = start; now < end; now += bucketDuration) { 297 // try finding existing bucket 298 final int index = Arrays.binarySearch(bucketStart, 0, bucketCount, now); 299 if (index < 0) { 300 // bucket missing, create and insert 301 insertBucket(~index, now); 302 } 303 } 304 } 305 306 /** 307 * Insert new bucket at requested index and starting time. 308 */ 309 private void insertBucket(int index, long start) { 310 // create more buckets when needed 311 if (bucketCount >= bucketStart.length) { 312 final int newLength = Math.max(bucketStart.length, 10) * 3 / 2; 313 bucketStart = Arrays.copyOf(bucketStart, newLength); 314 if (rxBytes != null) rxBytes = Arrays.copyOf(rxBytes, newLength); 315 if (rxPackets != null) rxPackets = Arrays.copyOf(rxPackets, newLength); 316 if (txBytes != null) txBytes = Arrays.copyOf(txBytes, newLength); 317 if (txPackets != null) txPackets = Arrays.copyOf(txPackets, newLength); 318 if (operations != null) operations = Arrays.copyOf(operations, newLength); 319 } 320 321 // create gap when inserting bucket in middle 322 if (index < bucketCount) { 323 final int dstPos = index + 1; 324 final int length = bucketCount - index; 325 326 System.arraycopy(bucketStart, index, bucketStart, dstPos, length); 327 if (rxBytes != null) System.arraycopy(rxBytes, index, rxBytes, dstPos, length); 328 if (rxPackets != null) System.arraycopy(rxPackets, index, rxPackets, dstPos, length); 329 if (txBytes != null) System.arraycopy(txBytes, index, txBytes, dstPos, length); 330 if (txPackets != null) System.arraycopy(txPackets, index, txPackets, dstPos, length); 331 if (operations != null) System.arraycopy(operations, index, operations, dstPos, length); 332 } 333 334 bucketStart[index] = start; 335 setLong(rxBytes, index, 0L); 336 setLong(rxPackets, index, 0L); 337 setLong(txBytes, index, 0L); 338 setLong(txPackets, index, 0L); 339 setLong(operations, index, 0L); 340 bucketCount++; 341 } 342 343 /** 344 * Remove buckets older than requested cutoff. 345 */ 346 public void removeBucketsBefore(long cutoff) { 347 int i; 348 for (i = 0; i < bucketCount; i++) { 349 final long curStart = bucketStart[i]; 350 final long curEnd = curStart + bucketDuration; 351 352 // cutoff happens before or during this bucket; everything before 353 // this bucket should be removed. 354 if (curEnd > cutoff) break; 355 } 356 357 if (i > 0) { 358 final int length = bucketStart.length; 359 bucketStart = Arrays.copyOfRange(bucketStart, i, length); 360 if (rxBytes != null) rxBytes = Arrays.copyOfRange(rxBytes, i, length); 361 if (rxPackets != null) rxPackets = Arrays.copyOfRange(rxPackets, i, length); 362 if (txBytes != null) txBytes = Arrays.copyOfRange(txBytes, i, length); 363 if (txPackets != null) txPackets = Arrays.copyOfRange(txPackets, i, length); 364 if (operations != null) operations = Arrays.copyOfRange(operations, i, length); 365 bucketCount -= i; 366 } 367 } 368 369 /** 370 * Return interpolated data usage across the requested range. Interpolates 371 * across buckets, so values may be rounded slightly. 372 */ 373 public Entry getValues(long start, long end, Entry recycle) { 374 return getValues(start, end, Long.MAX_VALUE, recycle); 375 } 376 377 /** 378 * Return interpolated data usage across the requested range. Interpolates 379 * across buckets, so values may be rounded slightly. 380 */ 381 public Entry getValues(long start, long end, long now, Entry recycle) { 382 final Entry entry = recycle != null ? recycle : new Entry(); 383 entry.bucketStart = start; 384 entry.bucketDuration = end - start; 385 entry.rxBytes = rxBytes != null ? 0 : UNKNOWN; 386 entry.rxPackets = rxPackets != null ? 0 : UNKNOWN; 387 entry.txBytes = txBytes != null ? 0 : UNKNOWN; 388 entry.txPackets = txPackets != null ? 0 : UNKNOWN; 389 entry.operations = operations != null ? 0 : UNKNOWN; 390 391 for (int i = bucketCount - 1; i >= 0; i--) { 392 final long curStart = bucketStart[i]; 393 final long curEnd = curStart + bucketDuration; 394 395 // bucket is older than record; we're finished 396 if (curEnd < start) break; 397 // bucket is newer than record; keep looking 398 if (curStart > end) continue; 399 400 // include full value for active buckets, otherwise only fractional 401 final boolean activeBucket = curStart < now && curEnd > now; 402 final long overlap = activeBucket ? bucketDuration 403 : Math.min(curEnd, end) - Math.max(curStart, start); 404 if (overlap <= 0) continue; 405 406 // integer math each time is faster than floating point 407 if (rxBytes != null) entry.rxBytes += rxBytes[i] * overlap / bucketDuration; 408 if (rxPackets != null) entry.rxPackets += rxPackets[i] * overlap / bucketDuration; 409 if (txBytes != null) entry.txBytes += txBytes[i] * overlap / bucketDuration; 410 if (txPackets != null) entry.txPackets += txPackets[i] * overlap / bucketDuration; 411 if (operations != null) entry.operations += operations[i] * overlap / bucketDuration; 412 } 413 414 return entry; 415 } 416 417 /** 418 * @deprecated only for temporary testing 419 */ 420 @Deprecated 421 public void generateRandom(long start, long end, long rxBytes, long rxPackets, long txBytes, 422 long txPackets, long operations) { 423 ensureBuckets(start, end); 424 425 final NetworkStats.Entry entry = new NetworkStats.Entry( 426 IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L); 427 final Random r = new Random(); 428 while (rxBytes > 1024 || rxPackets > 128 || txBytes > 1024 || txPackets > 128 429 || operations > 32) { 430 final long curStart = randomLong(r, start, end); 431 final long curEnd = randomLong(r, curStart, end); 432 433 entry.rxBytes = randomLong(r, 0, rxBytes); 434 entry.rxPackets = randomLong(r, 0, rxPackets); 435 entry.txBytes = randomLong(r, 0, txBytes); 436 entry.txPackets = randomLong(r, 0, txPackets); 437 entry.operations = randomLong(r, 0, operations); 438 439 rxBytes -= entry.rxBytes; 440 rxPackets -= entry.rxPackets; 441 txBytes -= entry.txBytes; 442 txPackets -= entry.txPackets; 443 operations -= entry.operations; 444 445 recordData(curStart, curEnd, entry); 446 } 447 } 448 449 private static long randomLong(Random r, long start, long end) { 450 return (long) (start + (r.nextFloat() * (end - start))); 451 } 452 453 public void dump(String prefix, PrintWriter pw, boolean fullHistory) { 454 pw.print(prefix); 455 pw.print("NetworkStatsHistory: bucketDuration="); pw.println(bucketDuration); 456 457 final int start = fullHistory ? 0 : Math.max(0, bucketCount - 32); 458 if (start > 0) { 459 pw.print(prefix); 460 pw.print(" (omitting "); pw.print(start); pw.println(" buckets)"); 461 } 462 463 for (int i = start; i < bucketCount; i++) { 464 pw.print(prefix); 465 pw.print(" bucketStart="); pw.print(bucketStart[i]); 466 if (rxBytes != null) pw.print(" rxBytes="); pw.print(rxBytes[i]); 467 if (rxPackets != null) pw.print(" rxPackets="); pw.print(rxPackets[i]); 468 if (txBytes != null) pw.print(" txBytes="); pw.print(txBytes[i]); 469 if (txPackets != null) pw.print(" txPackets="); pw.print(txPackets[i]); 470 if (operations != null) pw.print(" operations="); pw.print(operations[i]); 471 pw.println(); 472 } 473 } 474 475 @Override 476 public String toString() { 477 final CharArrayWriter writer = new CharArrayWriter(); 478 dump("", new PrintWriter(writer), false); 479 return writer.toString(); 480 } 481 482 public static final Creator<NetworkStatsHistory> CREATOR = new Creator<NetworkStatsHistory>() { 483 public NetworkStatsHistory createFromParcel(Parcel in) { 484 return new NetworkStatsHistory(in); 485 } 486 487 public NetworkStatsHistory[] newArray(int size) { 488 return new NetworkStatsHistory[size]; 489 } 490 }; 491 492 private static long getLong(long[] array, int i, long value) { 493 return array != null ? array[i] : value; 494 } 495 496 private static void setLong(long[] array, int i, long value) { 497 if (array != null) array[i] = value; 498 } 499 500 private static void addLong(long[] array, int i, long value) { 501 if (array != null) array[i] += value; 502 } 503 504 /** 505 * Utility methods for interacting with {@link DataInputStream} and 506 * {@link DataOutputStream}, mostly dealing with writing partial arrays. 507 */ 508 public static class DataStreamUtils { 509 @Deprecated 510 public static long[] readFullLongArray(DataInputStream in) throws IOException { 511 final int size = in.readInt(); 512 final long[] values = new long[size]; 513 for (int i = 0; i < values.length; i++) { 514 values[i] = in.readLong(); 515 } 516 return values; 517 } 518 519 /** 520 * Read variable-length {@link Long} using protobuf-style approach. 521 */ 522 public static long readVarLong(DataInputStream in) throws IOException { 523 int shift = 0; 524 long result = 0; 525 while (shift < 64) { 526 byte b = in.readByte(); 527 result |= (long) (b & 0x7F) << shift; 528 if ((b & 0x80) == 0) 529 return result; 530 shift += 7; 531 } 532 throw new ProtocolException("malformed long"); 533 } 534 535 /** 536 * Write variable-length {@link Long} using protobuf-style approach. 537 */ 538 public static void writeVarLong(DataOutputStream out, long value) throws IOException { 539 while (true) { 540 if ((value & ~0x7FL) == 0) { 541 out.writeByte((int) value); 542 return; 543 } else { 544 out.writeByte(((int) value & 0x7F) | 0x80); 545 value >>>= 7; 546 } 547 } 548 } 549 550 public static long[] readVarLongArray(DataInputStream in) throws IOException { 551 final int size = in.readInt(); 552 if (size == -1) return null; 553 final long[] values = new long[size]; 554 for (int i = 0; i < values.length; i++) { 555 values[i] = readVarLong(in); 556 } 557 return values; 558 } 559 560 public static void writeVarLongArray(DataOutputStream out, long[] values, int size) 561 throws IOException { 562 if (values == null) { 563 out.writeInt(-1); 564 return; 565 } 566 if (size > values.length) { 567 throw new IllegalArgumentException("size larger than length"); 568 } 569 out.writeInt(size); 570 for (int i = 0; i < size; i++) { 571 writeVarLong(out, values[i]); 572 } 573 } 574 } 575 576 /** 577 * Utility methods for interacting with {@link Parcel} structures, mostly 578 * dealing with writing partial arrays. 579 */ 580 public static class ParcelUtils { 581 public static long[] readLongArray(Parcel in) { 582 final int size = in.readInt(); 583 if (size == -1) return null; 584 final long[] values = new long[size]; 585 for (int i = 0; i < values.length; i++) { 586 values[i] = in.readLong(); 587 } 588 return values; 589 } 590 591 public static void writeLongArray(Parcel out, long[] values, int size) { 592 if (values == null) { 593 out.writeInt(-1); 594 return; 595 } 596 if (size > values.length) { 597 throw new IllegalArgumentException("size larger than length"); 598 } 599 out.writeInt(size); 600 for (int i = 0; i < size; i++) { 601 out.writeLong(values[i]); 602 } 603 } 604 } 605 606} 607