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 NetworkStatsHistory(long bucketDuration)99 public NetworkStatsHistory(long bucketDuration) { 100 this(bucketDuration, 10, FIELD_ALL); 101 } 102 NetworkStatsHistory(long bucketDuration, int initialSize)103 public NetworkStatsHistory(long bucketDuration, int initialSize) { 104 this(bucketDuration, initialSize, FIELD_ALL); 105 } 106 NetworkStatsHistory(long bucketDuration, int initialSize, int fields)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 NetworkStatsHistory(NetworkStatsHistory existing, long bucketDuration)120 public NetworkStatsHistory(NetworkStatsHistory existing, long bucketDuration) { 121 this(bucketDuration, existing.estimateResizeBuckets(bucketDuration)); 122 recordEntireHistory(existing); 123 } 124 NetworkStatsHistory(Parcel in)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 writeToParcel(Parcel out, int flags)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 NetworkStatsHistory(DataInputStream in)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 writeToStream(DataOutputStream out)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 describeContents()206 public int describeContents() { 207 return 0; 208 } 209 size()210 public int size() { 211 return bucketCount; 212 } 213 getBucketDuration()214 public long getBucketDuration() { 215 return bucketDuration; 216 } 217 getStart()218 public long getStart() { 219 if (bucketCount > 0) { 220 return bucketStart[0]; 221 } else { 222 return Long.MAX_VALUE; 223 } 224 } 225 getEnd()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 */ getTotalBytes()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 */ getIndexBefore(long time)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 */ getIndexAfter(long time)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 */ getValues(int i, Entry recycle)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 recordData(long start, long end, long rxBytes, long txBytes)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 */ recordData(long start, long end, NetworkStats.Entry entry)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 */ recordEntireHistory(NetworkStatsHistory input)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 */ recordHistory(NetworkStatsHistory input, long start, long end)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 */ ensureBuckets(long start, long end)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 */ insertBucket(int index, long start)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 removeBucketsBefore(long cutoff)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 */ getValues(long start, long end, Entry recycle)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 */ getValues(long start, long end, long now, Entry recycle)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 generateRandom(long start, long end, long bytes)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 generateRandom(long start, long end, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations, Random r)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 randomLong(Random r, long start, long end)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 */ intersects(long start, long end)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 dump(IndentingPrintWriter pw, boolean fullHistory)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 dumpCheckin(PrintWriter pw)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 writeToProto(ProtoOutputStream proto, long tag)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 writeToProto(ProtoOutputStream proto, long tag, long[] array, int index)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 toString()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 getLong(long[] array, int i, long value)680 private static long getLong(long[] array, int i, long value) { 681 return array != null ? array[i] : value; 682 } 683 setLong(long[] array, int i, long value)684 private static void setLong(long[] array, int i, long value) { 685 if (array != null) array[i] = value; 686 } 687 addLong(long[] array, int i, long value)688 private static void addLong(long[] array, int i, long value) { 689 if (array != null) array[i] += value; 690 } 691 estimateResizeBuckets(long newBucketDuration)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 readFullLongArray(DataInputStream in)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 */ readVarLong(DataInputStream in)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 */ writeVarLong(DataOutputStream out, long value)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 readVarLongArray(DataInputStream in)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 writeVarLongArray(DataOutputStream out, long[] values, int size)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 { readLongArray(Parcel in)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 writeLongArray(Parcel out, long[] values, int size)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