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 96 public NetworkStatsHistory(long bucketDuration) { 97 this(bucketDuration, 10, FIELD_ALL); 98 } 99 100 public NetworkStatsHistory(long bucketDuration, int initialSize) { 101 this(bucketDuration, initialSize, FIELD_ALL); 102 } 103 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 117 public NetworkStatsHistory(NetworkStatsHistory existing, long bucketDuration) { 118 this(bucketDuration, existing.estimateResizeBuckets(bucketDuration)); 119 recordEntireHistory(existing); 120 } 121 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 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 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 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 203 public int describeContents() { 204 return 0; 205 } 206 207 public int size() { 208 return bucketCount; 209 } 210 211 public long getBucketDuration() { 212 return bucketDuration; 213 } 214 215 public long getStart() { 216 if (bucketCount > 0) { 217 return bucketStart[0]; 218 } else { 219 return Long.MAX_VALUE; 220 } 221 } 222 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 */ 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 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 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 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 */ 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 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 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 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 650 private static long getLong(long[] array, int i, long value) { 651 return array != null ? array[i] : value; 652 } 653 654 private static void setLong(long[] array, int i, long value) { 655 if (array != null) array[i] = value; 656 } 657 658 private static void addLong(long[] array, int i, long value) { 659 if (array != null) array[i] += value; 660 } 661 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 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 */ 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 */ 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 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 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 { 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 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