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