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