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 17 package android.net; 18 19 import static android.net.NetworkStats.IFACE_ALL; 20 import static android.net.NetworkStats.SET_DEFAULT; 21 import static android.net.NetworkStats.TAG_NONE; 22 import static android.net.NetworkStats.UID_ALL; 23 import static android.net.NetworkStatsHistory.DataStreamUtils.readFullLongArray; 24 import static android.net.NetworkStatsHistory.DataStreamUtils.readVarLongArray; 25 import static android.net.NetworkStatsHistory.DataStreamUtils.writeVarLongArray; 26 import static android.net.NetworkStatsHistory.Entry.UNKNOWN; 27 import static android.net.NetworkStatsHistory.ParcelUtils.readLongArray; 28 import static android.net.NetworkStatsHistory.ParcelUtils.writeLongArray; 29 import static android.text.format.DateUtils.SECOND_IN_MILLIS; 30 import static com.android.internal.util.ArrayUtils.total; 31 32 import android.os.Parcel; 33 import android.os.Parcelable; 34 import android.util.MathUtils; 35 36 import com.android.internal.util.IndentingPrintWriter; 37 38 import java.io.CharArrayWriter; 39 import java.io.DataInputStream; 40 import java.io.DataOutputStream; 41 import java.io.IOException; 42 import java.io.PrintWriter; 43 import java.net.ProtocolException; 44 import java.util.Arrays; 45 import java.util.Random; 46 47 /** 48 * Collection of historical network statistics, recorded into equally-sized 49 * "buckets" in time. Internally it stores data in {@code long} series for more 50 * efficient persistence. 51 * <p> 52 * Each bucket is defined by a {@link #bucketStart} timestamp, and lasts for 53 * {@link #bucketDuration}. Internally assumes that {@link #bucketStart} is 54 * sorted at all times. 55 * 56 * @hide 57 */ 58 public class NetworkStatsHistory implements Parcelable { 59 private static final int VERSION_INIT = 1; 60 private static final int VERSION_ADD_PACKETS = 2; 61 private static final int VERSION_ADD_ACTIVE = 3; 62 63 public static final int FIELD_ACTIVE_TIME = 0x01; 64 public static final int FIELD_RX_BYTES = 0x02; 65 public static final int FIELD_RX_PACKETS = 0x04; 66 public static final int FIELD_TX_BYTES = 0x08; 67 public static final int FIELD_TX_PACKETS = 0x10; 68 public static final int FIELD_OPERATIONS = 0x20; 69 70 public static final int FIELD_ALL = 0xFFFFFFFF; 71 72 private long bucketDuration; 73 private int bucketCount; 74 private long[] bucketStart; 75 private long[] activeTime; 76 private long[] rxBytes; 77 private long[] rxPackets; 78 private long[] txBytes; 79 private long[] txPackets; 80 private long[] operations; 81 private long totalBytes; 82 83 public static class Entry { 84 public static final long UNKNOWN = -1; 85 86 public long bucketDuration; 87 public long bucketStart; 88 public long activeTime; 89 public long rxBytes; 90 public long rxPackets; 91 public long txBytes; 92 public long txPackets; 93 public long operations; 94 } 95 NetworkStatsHistory(long bucketDuration)96 public NetworkStatsHistory(long bucketDuration) { 97 this(bucketDuration, 10, FIELD_ALL); 98 } 99 NetworkStatsHistory(long bucketDuration, int initialSize)100 public NetworkStatsHistory(long bucketDuration, int initialSize) { 101 this(bucketDuration, initialSize, FIELD_ALL); 102 } 103 NetworkStatsHistory(long bucketDuration, int initialSize, int fields)104 public NetworkStatsHistory(long bucketDuration, int initialSize, int fields) { 105 this.bucketDuration = bucketDuration; 106 bucketStart = new long[initialSize]; 107 if ((fields & FIELD_ACTIVE_TIME) != 0) activeTime = new long[initialSize]; 108 if ((fields & FIELD_RX_BYTES) != 0) rxBytes = new long[initialSize]; 109 if ((fields & FIELD_RX_PACKETS) != 0) rxPackets = new long[initialSize]; 110 if ((fields & FIELD_TX_BYTES) != 0) txBytes = new long[initialSize]; 111 if ((fields & FIELD_TX_PACKETS) != 0) txPackets = new long[initialSize]; 112 if ((fields & FIELD_OPERATIONS) != 0) operations = new long[initialSize]; 113 bucketCount = 0; 114 totalBytes = 0; 115 } 116 NetworkStatsHistory(NetworkStatsHistory existing, long bucketDuration)117 public NetworkStatsHistory(NetworkStatsHistory existing, long bucketDuration) { 118 this(bucketDuration, existing.estimateResizeBuckets(bucketDuration)); 119 recordEntireHistory(existing); 120 } 121 NetworkStatsHistory(Parcel in)122 public NetworkStatsHistory(Parcel in) { 123 bucketDuration = in.readLong(); 124 bucketStart = readLongArray(in); 125 activeTime = readLongArray(in); 126 rxBytes = readLongArray(in); 127 rxPackets = readLongArray(in); 128 txBytes = readLongArray(in); 129 txPackets = readLongArray(in); 130 operations = readLongArray(in); 131 bucketCount = bucketStart.length; 132 totalBytes = in.readLong(); 133 } 134 135 @Override writeToParcel(Parcel out, int flags)136 public void writeToParcel(Parcel out, int flags) { 137 out.writeLong(bucketDuration); 138 writeLongArray(out, bucketStart, bucketCount); 139 writeLongArray(out, activeTime, bucketCount); 140 writeLongArray(out, rxBytes, bucketCount); 141 writeLongArray(out, rxPackets, bucketCount); 142 writeLongArray(out, txBytes, bucketCount); 143 writeLongArray(out, txPackets, bucketCount); 144 writeLongArray(out, operations, bucketCount); 145 out.writeLong(totalBytes); 146 } 147 NetworkStatsHistory(DataInputStream in)148 public NetworkStatsHistory(DataInputStream in) throws IOException { 149 final int version = in.readInt(); 150 switch (version) { 151 case VERSION_INIT: { 152 bucketDuration = in.readLong(); 153 bucketStart = readFullLongArray(in); 154 rxBytes = readFullLongArray(in); 155 rxPackets = new long[bucketStart.length]; 156 txBytes = readFullLongArray(in); 157 txPackets = new long[bucketStart.length]; 158 operations = new long[bucketStart.length]; 159 bucketCount = bucketStart.length; 160 totalBytes = total(rxBytes) + total(txBytes); 161 break; 162 } 163 case VERSION_ADD_PACKETS: 164 case VERSION_ADD_ACTIVE: { 165 bucketDuration = in.readLong(); 166 bucketStart = readVarLongArray(in); 167 activeTime = (version >= VERSION_ADD_ACTIVE) ? readVarLongArray(in) 168 : new long[bucketStart.length]; 169 rxBytes = readVarLongArray(in); 170 rxPackets = readVarLongArray(in); 171 txBytes = readVarLongArray(in); 172 txPackets = readVarLongArray(in); 173 operations = readVarLongArray(in); 174 bucketCount = bucketStart.length; 175 totalBytes = total(rxBytes) + total(txBytes); 176 break; 177 } 178 default: { 179 throw new ProtocolException("unexpected version: " + version); 180 } 181 } 182 183 if (bucketStart.length != bucketCount || rxBytes.length != bucketCount 184 || rxPackets.length != bucketCount || txBytes.length != bucketCount 185 || txPackets.length != bucketCount || operations.length != bucketCount) { 186 throw new ProtocolException("Mismatched history lengths"); 187 } 188 } 189 writeToStream(DataOutputStream out)190 public void writeToStream(DataOutputStream out) throws IOException { 191 out.writeInt(VERSION_ADD_ACTIVE); 192 out.writeLong(bucketDuration); 193 writeVarLongArray(out, bucketStart, bucketCount); 194 writeVarLongArray(out, activeTime, bucketCount); 195 writeVarLongArray(out, rxBytes, bucketCount); 196 writeVarLongArray(out, rxPackets, bucketCount); 197 writeVarLongArray(out, txBytes, bucketCount); 198 writeVarLongArray(out, txPackets, bucketCount); 199 writeVarLongArray(out, operations, bucketCount); 200 } 201 202 @Override describeContents()203 public int describeContents() { 204 return 0; 205 } 206 size()207 public int size() { 208 return bucketCount; 209 } 210 getBucketDuration()211 public long getBucketDuration() { 212 return bucketDuration; 213 } 214 getStart()215 public long getStart() { 216 if (bucketCount > 0) { 217 return bucketStart[0]; 218 } else { 219 return Long.MAX_VALUE; 220 } 221 } 222 getEnd()223 public long getEnd() { 224 if (bucketCount > 0) { 225 return bucketStart[bucketCount - 1] + bucketDuration; 226 } else { 227 return Long.MIN_VALUE; 228 } 229 } 230 231 /** 232 * Return total bytes represented by this history. 233 */ getTotalBytes()234 public long getTotalBytes() { 235 return totalBytes; 236 } 237 238 /** 239 * Return index of bucket that contains or is immediately before the 240 * requested time. 241 */ getIndexBefore(long time)242 public int getIndexBefore(long time) { 243 int index = Arrays.binarySearch(bucketStart, 0, bucketCount, time); 244 if (index < 0) { 245 index = (~index) - 1; 246 } else { 247 index -= 1; 248 } 249 return MathUtils.constrain(index, 0, bucketCount - 1); 250 } 251 252 /** 253 * Return index of bucket that contains or is immediately after the 254 * requested time. 255 */ getIndexAfter(long time)256 public int getIndexAfter(long time) { 257 int index = Arrays.binarySearch(bucketStart, 0, bucketCount, time); 258 if (index < 0) { 259 index = ~index; 260 } else { 261 index += 1; 262 } 263 return MathUtils.constrain(index, 0, bucketCount - 1); 264 } 265 266 /** 267 * Return specific stats entry. 268 */ getValues(int i, Entry recycle)269 public Entry getValues(int i, Entry recycle) { 270 final Entry entry = recycle != null ? recycle : new Entry(); 271 entry.bucketStart = bucketStart[i]; 272 entry.bucketDuration = bucketDuration; 273 entry.activeTime = getLong(activeTime, i, UNKNOWN); 274 entry.rxBytes = getLong(rxBytes, i, UNKNOWN); 275 entry.rxPackets = getLong(rxPackets, i, UNKNOWN); 276 entry.txBytes = getLong(txBytes, i, UNKNOWN); 277 entry.txPackets = getLong(txPackets, i, UNKNOWN); 278 entry.operations = getLong(operations, i, UNKNOWN); 279 return entry; 280 } 281 282 /** 283 * Record that data traffic occurred in the given time range. Will 284 * distribute across internal buckets, creating new buckets as needed. 285 */ 286 @Deprecated recordData(long start, long end, long rxBytes, long txBytes)287 public void recordData(long start, long end, long rxBytes, long txBytes) { 288 recordData(start, end, new NetworkStats.Entry( 289 IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, 0L, txBytes, 0L, 0L)); 290 } 291 292 /** 293 * Record that data traffic occurred in the given time range. Will 294 * distribute across internal buckets, creating new buckets as needed. 295 */ recordData(long start, long end, NetworkStats.Entry entry)296 public void recordData(long start, long end, NetworkStats.Entry entry) { 297 long rxBytes = entry.rxBytes; 298 long rxPackets = entry.rxPackets; 299 long txBytes = entry.txBytes; 300 long txPackets = entry.txPackets; 301 long operations = entry.operations; 302 303 if (entry.isNegative()) { 304 throw new IllegalArgumentException("tried recording negative data"); 305 } 306 if (entry.isEmpty()) { 307 return; 308 } 309 310 // create any buckets needed by this range 311 ensureBuckets(start, end); 312 313 // distribute data usage into buckets 314 long duration = end - start; 315 final int startIndex = getIndexAfter(end); 316 for (int i = startIndex; i >= 0; i--) { 317 final long curStart = bucketStart[i]; 318 final long curEnd = curStart + bucketDuration; 319 320 // bucket is older than record; we're finished 321 if (curEnd < start) break; 322 // bucket is newer than record; keep looking 323 if (curStart > end) continue; 324 325 final long overlap = Math.min(curEnd, end) - Math.max(curStart, start); 326 if (overlap <= 0) continue; 327 328 // integer math each time is faster than floating point 329 final long fracRxBytes = rxBytes * overlap / duration; 330 final long fracRxPackets = rxPackets * overlap / duration; 331 final long fracTxBytes = txBytes * overlap / duration; 332 final long fracTxPackets = txPackets * overlap / duration; 333 final long fracOperations = operations * overlap / duration; 334 335 addLong(activeTime, i, overlap); 336 addLong(this.rxBytes, i, fracRxBytes); rxBytes -= fracRxBytes; 337 addLong(this.rxPackets, i, fracRxPackets); rxPackets -= fracRxPackets; 338 addLong(this.txBytes, i, fracTxBytes); txBytes -= fracTxBytes; 339 addLong(this.txPackets, i, fracTxPackets); txPackets -= fracTxPackets; 340 addLong(this.operations, i, fracOperations); operations -= fracOperations; 341 342 duration -= overlap; 343 } 344 345 totalBytes += entry.rxBytes + entry.txBytes; 346 } 347 348 /** 349 * Record an entire {@link NetworkStatsHistory} into this history. Usually 350 * for combining together stats for external reporting. 351 */ recordEntireHistory(NetworkStatsHistory input)352 public void recordEntireHistory(NetworkStatsHistory input) { 353 recordHistory(input, Long.MIN_VALUE, Long.MAX_VALUE); 354 } 355 356 /** 357 * Record given {@link NetworkStatsHistory} into this history, copying only 358 * buckets that atomically occur in the inclusive time range. Doesn't 359 * interpolate across partial buckets. 360 */ recordHistory(NetworkStatsHistory input, long start, long end)361 public void recordHistory(NetworkStatsHistory input, long start, long end) { 362 final NetworkStats.Entry entry = new NetworkStats.Entry( 363 IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L); 364 for (int i = 0; i < input.bucketCount; i++) { 365 final long bucketStart = input.bucketStart[i]; 366 final long bucketEnd = bucketStart + input.bucketDuration; 367 368 // skip when bucket is outside requested range 369 if (bucketStart < start || bucketEnd > end) continue; 370 371 entry.rxBytes = getLong(input.rxBytes, i, 0L); 372 entry.rxPackets = getLong(input.rxPackets, i, 0L); 373 entry.txBytes = getLong(input.txBytes, i, 0L); 374 entry.txPackets = getLong(input.txPackets, i, 0L); 375 entry.operations = getLong(input.operations, i, 0L); 376 377 recordData(bucketStart, bucketEnd, entry); 378 } 379 } 380 381 /** 382 * Ensure that buckets exist for given time range, creating as needed. 383 */ ensureBuckets(long start, long end)384 private void ensureBuckets(long start, long end) { 385 // normalize incoming range to bucket boundaries 386 start -= start % bucketDuration; 387 end += (bucketDuration - (end % bucketDuration)) % bucketDuration; 388 389 for (long now = start; now < end; now += bucketDuration) { 390 // try finding existing bucket 391 final int index = Arrays.binarySearch(bucketStart, 0, bucketCount, now); 392 if (index < 0) { 393 // bucket missing, create and insert 394 insertBucket(~index, now); 395 } 396 } 397 } 398 399 /** 400 * Insert new bucket at requested index and starting time. 401 */ insertBucket(int index, long start)402 private void insertBucket(int index, long start) { 403 // create more buckets when needed 404 if (bucketCount >= bucketStart.length) { 405 final int newLength = Math.max(bucketStart.length, 10) * 3 / 2; 406 bucketStart = Arrays.copyOf(bucketStart, newLength); 407 if (activeTime != null) activeTime = Arrays.copyOf(activeTime, newLength); 408 if (rxBytes != null) rxBytes = Arrays.copyOf(rxBytes, newLength); 409 if (rxPackets != null) rxPackets = Arrays.copyOf(rxPackets, newLength); 410 if (txBytes != null) txBytes = Arrays.copyOf(txBytes, newLength); 411 if (txPackets != null) txPackets = Arrays.copyOf(txPackets, newLength); 412 if (operations != null) operations = Arrays.copyOf(operations, newLength); 413 } 414 415 // create gap when inserting bucket in middle 416 if (index < bucketCount) { 417 final int dstPos = index + 1; 418 final int length = bucketCount - index; 419 420 System.arraycopy(bucketStart, index, bucketStart, dstPos, length); 421 if (activeTime != null) System.arraycopy(activeTime, index, activeTime, dstPos, length); 422 if (rxBytes != null) System.arraycopy(rxBytes, index, rxBytes, dstPos, length); 423 if (rxPackets != null) System.arraycopy(rxPackets, index, rxPackets, dstPos, length); 424 if (txBytes != null) System.arraycopy(txBytes, index, txBytes, dstPos, length); 425 if (txPackets != null) System.arraycopy(txPackets, index, txPackets, dstPos, length); 426 if (operations != null) System.arraycopy(operations, index, operations, dstPos, length); 427 } 428 429 bucketStart[index] = start; 430 setLong(activeTime, index, 0L); 431 setLong(rxBytes, index, 0L); 432 setLong(rxPackets, index, 0L); 433 setLong(txBytes, index, 0L); 434 setLong(txPackets, index, 0L); 435 setLong(operations, index, 0L); 436 bucketCount++; 437 } 438 439 /** 440 * Remove buckets older than requested cutoff. 441 */ 442 @Deprecated removeBucketsBefore(long cutoff)443 public void removeBucketsBefore(long cutoff) { 444 int i; 445 for (i = 0; i < bucketCount; i++) { 446 final long curStart = bucketStart[i]; 447 final long curEnd = curStart + bucketDuration; 448 449 // cutoff happens before or during this bucket; everything before 450 // this bucket should be removed. 451 if (curEnd > cutoff) break; 452 } 453 454 if (i > 0) { 455 final int length = bucketStart.length; 456 bucketStart = Arrays.copyOfRange(bucketStart, i, length); 457 if (activeTime != null) activeTime = Arrays.copyOfRange(activeTime, i, length); 458 if (rxBytes != null) rxBytes = Arrays.copyOfRange(rxBytes, i, length); 459 if (rxPackets != null) rxPackets = Arrays.copyOfRange(rxPackets, i, length); 460 if (txBytes != null) txBytes = Arrays.copyOfRange(txBytes, i, length); 461 if (txPackets != null) txPackets = Arrays.copyOfRange(txPackets, i, length); 462 if (operations != null) operations = Arrays.copyOfRange(operations, i, length); 463 bucketCount -= i; 464 465 // TODO: subtract removed values from totalBytes 466 } 467 } 468 469 /** 470 * Return interpolated data usage across the requested range. Interpolates 471 * across buckets, so values may be rounded slightly. 472 */ getValues(long start, long end, Entry recycle)473 public Entry getValues(long start, long end, Entry recycle) { 474 return getValues(start, end, Long.MAX_VALUE, recycle); 475 } 476 477 /** 478 * Return interpolated data usage across the requested range. Interpolates 479 * across buckets, so values may be rounded slightly. 480 */ getValues(long start, long end, long now, Entry recycle)481 public Entry getValues(long start, long end, long now, Entry recycle) { 482 final Entry entry = recycle != null ? recycle : new Entry(); 483 entry.bucketDuration = end - start; 484 entry.bucketStart = start; 485 entry.activeTime = activeTime != null ? 0 : UNKNOWN; 486 entry.rxBytes = rxBytes != null ? 0 : UNKNOWN; 487 entry.rxPackets = rxPackets != null ? 0 : UNKNOWN; 488 entry.txBytes = txBytes != null ? 0 : UNKNOWN; 489 entry.txPackets = txPackets != null ? 0 : UNKNOWN; 490 entry.operations = operations != null ? 0 : UNKNOWN; 491 492 final int startIndex = getIndexAfter(end); 493 for (int i = startIndex; i >= 0; i--) { 494 final long curStart = bucketStart[i]; 495 final long curEnd = curStart + bucketDuration; 496 497 // bucket is older than request; we're finished 498 if (curEnd <= start) break; 499 // bucket is newer than request; keep looking 500 if (curStart >= end) continue; 501 502 // include full value for active buckets, otherwise only fractional 503 final boolean activeBucket = curStart < now && curEnd > now; 504 final long overlap; 505 if (activeBucket) { 506 overlap = bucketDuration; 507 } else { 508 final long overlapEnd = curEnd < end ? curEnd : end; 509 final long overlapStart = curStart > start ? curStart : start; 510 overlap = overlapEnd - overlapStart; 511 } 512 if (overlap <= 0) continue; 513 514 // integer math each time is faster than floating point 515 if (activeTime != null) entry.activeTime += activeTime[i] * overlap / bucketDuration; 516 if (rxBytes != null) entry.rxBytes += rxBytes[i] * overlap / bucketDuration; 517 if (rxPackets != null) entry.rxPackets += rxPackets[i] * overlap / bucketDuration; 518 if (txBytes != null) entry.txBytes += txBytes[i] * overlap / bucketDuration; 519 if (txPackets != null) entry.txPackets += txPackets[i] * overlap / bucketDuration; 520 if (operations != null) entry.operations += operations[i] * overlap / bucketDuration; 521 } 522 return entry; 523 } 524 525 /** 526 * @deprecated only for temporary testing 527 */ 528 @Deprecated generateRandom(long start, long end, long bytes)529 public void generateRandom(long start, long end, long bytes) { 530 final Random r = new Random(); 531 532 final float fractionRx = r.nextFloat(); 533 final long rxBytes = (long) (bytes * fractionRx); 534 final long txBytes = (long) (bytes * (1 - fractionRx)); 535 536 final long rxPackets = rxBytes / 1024; 537 final long txPackets = txBytes / 1024; 538 final long operations = rxBytes / 2048; 539 540 generateRandom(start, end, rxBytes, rxPackets, txBytes, txPackets, operations, r); 541 } 542 543 /** 544 * @deprecated only for temporary testing 545 */ 546 @Deprecated generateRandom(long start, long end, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations, Random r)547 public void generateRandom(long start, long end, long rxBytes, long rxPackets, long txBytes, 548 long txPackets, long operations, Random r) { 549 ensureBuckets(start, end); 550 551 final NetworkStats.Entry entry = new NetworkStats.Entry( 552 IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L); 553 while (rxBytes > 1024 || rxPackets > 128 || txBytes > 1024 || txPackets > 128 554 || operations > 32) { 555 final long curStart = randomLong(r, start, end); 556 final long curEnd = curStart + randomLong(r, 0, (end - curStart) / 2); 557 558 entry.rxBytes = randomLong(r, 0, rxBytes); 559 entry.rxPackets = randomLong(r, 0, rxPackets); 560 entry.txBytes = randomLong(r, 0, txBytes); 561 entry.txPackets = randomLong(r, 0, txPackets); 562 entry.operations = randomLong(r, 0, operations); 563 564 rxBytes -= entry.rxBytes; 565 rxPackets -= entry.rxPackets; 566 txBytes -= entry.txBytes; 567 txPackets -= entry.txPackets; 568 operations -= entry.operations; 569 570 recordData(curStart, curEnd, entry); 571 } 572 } 573 randomLong(Random r, long start, long end)574 public static long randomLong(Random r, long start, long end) { 575 return (long) (start + (r.nextFloat() * (end - start))); 576 } 577 578 /** 579 * Quickly determine if this history intersects with given window. 580 */ intersects(long start, long end)581 public boolean intersects(long start, long end) { 582 final long dataStart = getStart(); 583 final long dataEnd = getEnd(); 584 if (start >= dataStart && start <= dataEnd) return true; 585 if (end >= dataStart && end <= dataEnd) return true; 586 if (dataStart >= start && dataStart <= end) return true; 587 if (dataEnd >= start && dataEnd <= end) return true; 588 return false; 589 } 590 dump(IndentingPrintWriter pw, boolean fullHistory)591 public void dump(IndentingPrintWriter pw, boolean fullHistory) { 592 pw.print("NetworkStatsHistory: bucketDuration="); 593 pw.println(bucketDuration / SECOND_IN_MILLIS); 594 pw.increaseIndent(); 595 596 final int start = fullHistory ? 0 : Math.max(0, bucketCount - 32); 597 if (start > 0) { 598 pw.print("(omitting "); pw.print(start); pw.println(" buckets)"); 599 } 600 601 for (int i = start; i < bucketCount; i++) { 602 pw.print("st="); pw.print(bucketStart[i] / SECOND_IN_MILLIS); 603 if (rxBytes != null) { pw.print(" rb="); pw.print(rxBytes[i]); } 604 if (rxPackets != null) { pw.print(" rp="); pw.print(rxPackets[i]); } 605 if (txBytes != null) { pw.print(" tb="); pw.print(txBytes[i]); } 606 if (txPackets != null) { pw.print(" tp="); pw.print(txPackets[i]); } 607 if (operations != null) { pw.print(" op="); pw.print(operations[i]); } 608 pw.println(); 609 } 610 611 pw.decreaseIndent(); 612 } 613 dumpCheckin(PrintWriter pw)614 public void dumpCheckin(PrintWriter pw) { 615 pw.print("d,"); 616 pw.print(bucketDuration / SECOND_IN_MILLIS); 617 pw.println(); 618 619 for (int i = 0; i < bucketCount; i++) { 620 pw.print("b,"); 621 pw.print(bucketStart[i] / SECOND_IN_MILLIS); pw.print(','); 622 if (rxBytes != null) { pw.print(rxBytes[i]); } else { pw.print("*"); } pw.print(','); 623 if (rxPackets != null) { pw.print(rxPackets[i]); } else { pw.print("*"); } pw.print(','); 624 if (txBytes != null) { pw.print(txBytes[i]); } else { pw.print("*"); } pw.print(','); 625 if (txPackets != null) { pw.print(txPackets[i]); } else { pw.print("*"); } pw.print(','); 626 if (operations != null) { pw.print(operations[i]); } else { pw.print("*"); } 627 pw.println(); 628 } 629 } 630 631 @Override toString()632 public String toString() { 633 final CharArrayWriter writer = new CharArrayWriter(); 634 dump(new IndentingPrintWriter(writer, " "), false); 635 return writer.toString(); 636 } 637 638 public static final Creator<NetworkStatsHistory> CREATOR = new Creator<NetworkStatsHistory>() { 639 @Override 640 public NetworkStatsHistory createFromParcel(Parcel in) { 641 return new NetworkStatsHistory(in); 642 } 643 644 @Override 645 public NetworkStatsHistory[] newArray(int size) { 646 return new NetworkStatsHistory[size]; 647 } 648 }; 649 getLong(long[] array, int i, long value)650 private static long getLong(long[] array, int i, long value) { 651 return array != null ? array[i] : value; 652 } 653 setLong(long[] array, int i, long value)654 private static void setLong(long[] array, int i, long value) { 655 if (array != null) array[i] = value; 656 } 657 addLong(long[] array, int i, long value)658 private static void addLong(long[] array, int i, long value) { 659 if (array != null) array[i] += value; 660 } 661 estimateResizeBuckets(long newBucketDuration)662 public int estimateResizeBuckets(long newBucketDuration) { 663 return (int) (size() * getBucketDuration() / newBucketDuration); 664 } 665 666 /** 667 * Utility methods for interacting with {@link DataInputStream} and 668 * {@link DataOutputStream}, mostly dealing with writing partial arrays. 669 */ 670 public static class DataStreamUtils { 671 @Deprecated readFullLongArray(DataInputStream in)672 public static long[] readFullLongArray(DataInputStream in) throws IOException { 673 final int size = in.readInt(); 674 if (size < 0) throw new ProtocolException("negative array size"); 675 final long[] values = new long[size]; 676 for (int i = 0; i < values.length; i++) { 677 values[i] = in.readLong(); 678 } 679 return values; 680 } 681 682 /** 683 * Read variable-length {@link Long} using protobuf-style approach. 684 */ readVarLong(DataInputStream in)685 public static long readVarLong(DataInputStream in) throws IOException { 686 int shift = 0; 687 long result = 0; 688 while (shift < 64) { 689 byte b = in.readByte(); 690 result |= (long) (b & 0x7F) << shift; 691 if ((b & 0x80) == 0) 692 return result; 693 shift += 7; 694 } 695 throw new ProtocolException("malformed long"); 696 } 697 698 /** 699 * Write variable-length {@link Long} using protobuf-style approach. 700 */ writeVarLong(DataOutputStream out, long value)701 public static void writeVarLong(DataOutputStream out, long value) throws IOException { 702 while (true) { 703 if ((value & ~0x7FL) == 0) { 704 out.writeByte((int) value); 705 return; 706 } else { 707 out.writeByte(((int) value & 0x7F) | 0x80); 708 value >>>= 7; 709 } 710 } 711 } 712 readVarLongArray(DataInputStream in)713 public static long[] readVarLongArray(DataInputStream in) throws IOException { 714 final int size = in.readInt(); 715 if (size == -1) return null; 716 if (size < 0) throw new ProtocolException("negative array size"); 717 final long[] values = new long[size]; 718 for (int i = 0; i < values.length; i++) { 719 values[i] = readVarLong(in); 720 } 721 return values; 722 } 723 writeVarLongArray(DataOutputStream out, long[] values, int size)724 public static void writeVarLongArray(DataOutputStream out, long[] values, int size) 725 throws IOException { 726 if (values == null) { 727 out.writeInt(-1); 728 return; 729 } 730 if (size > values.length) { 731 throw new IllegalArgumentException("size larger than length"); 732 } 733 out.writeInt(size); 734 for (int i = 0; i < size; i++) { 735 writeVarLong(out, values[i]); 736 } 737 } 738 } 739 740 /** 741 * Utility methods for interacting with {@link Parcel} structures, mostly 742 * dealing with writing partial arrays. 743 */ 744 public static class ParcelUtils { readLongArray(Parcel in)745 public static long[] readLongArray(Parcel in) { 746 final int size = in.readInt(); 747 if (size == -1) return null; 748 final long[] values = new long[size]; 749 for (int i = 0; i < values.length; i++) { 750 values[i] = in.readLong(); 751 } 752 return values; 753 } 754 writeLongArray(Parcel out, long[] values, int size)755 public static void writeLongArray(Parcel out, long[] values, int size) { 756 if (values == null) { 757 out.writeInt(-1); 758 return; 759 } 760 if (size > values.length) { 761 throw new IllegalArgumentException("size larger than length"); 762 } 763 out.writeInt(size); 764 for (int i = 0; i < size; i++) { 765 out.writeLong(values[i]); 766 } 767 } 768 } 769 770 } 771