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