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 NetworkStatsHistory(long bucketDuration)102 public NetworkStatsHistory(long bucketDuration) { 103 this(bucketDuration, 10, FIELD_ALL); 104 } 105 NetworkStatsHistory(long bucketDuration, int initialSize)106 public NetworkStatsHistory(long bucketDuration, int initialSize) { 107 this(bucketDuration, initialSize, FIELD_ALL); 108 } 109 NetworkStatsHistory(long bucketDuration, int initialSize, int fields)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 NetworkStatsHistory(NetworkStatsHistory existing, long bucketDuration)123 public NetworkStatsHistory(NetworkStatsHistory existing, long bucketDuration) { 124 this(bucketDuration, existing.estimateResizeBuckets(bucketDuration)); 125 recordEntireHistory(existing); 126 } 127 NetworkStatsHistory(Parcel in)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 writeToParcel(Parcel out, int flags)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 NetworkStatsHistory(DataInputStream in)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 writeToStream(DataOutputStream out)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 describeContents()209 public int describeContents() { 210 return 0; 211 } 212 size()213 public int size() { 214 return bucketCount; 215 } 216 getBucketDuration()217 public long getBucketDuration() { 218 return bucketDuration; 219 } 220 getStart()221 public long getStart() { 222 if (bucketCount > 0) { 223 return bucketStart[0]; 224 } else { 225 return Long.MAX_VALUE; 226 } 227 } 228 getEnd()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 */ getTotalBytes()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 */ getIndexBefore(long time)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 */ getIndexAfter(long time)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 */ getValues(int i, Entry recycle)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 setValues(int i, Entry entry)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 recordData(long start, long end, long rxBytes, long txBytes)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 */ recordData(long start, long end, NetworkStats.Entry entry)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 */ recordEntireHistory(NetworkStatsHistory input)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 */ recordHistory(NetworkStatsHistory input, long start, long end)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 */ ensureBuckets(long start, long end)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 */ insertBucket(int index, long start)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 */ clear()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 removeBucketsBefore(long cutoff)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 */ getValues(long start, long end, Entry recycle)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 */ getValues(long start, long end, long now, Entry recycle)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 generateRandom(long start, long end, long bytes)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 generateRandom(long start, long end, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations, Random r)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 randomLong(Random r, long start, long end)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 */ intersects(long start, long end)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 dump(IndentingPrintWriter pw, boolean fullHistory)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 dumpCheckin(PrintWriter pw)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 writeToProto(ProtoOutputStream proto, long tag)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 writeToProto(ProtoOutputStream proto, long tag, long[] array, int index)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 toString()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 getLong(long[] array, int i, long value)716 private static long getLong(long[] array, int i, long value) { 717 return array != null ? array[i] : value; 718 } 719 setLong(long[] array, int i, long value)720 private static void setLong(long[] array, int i, long value) { 721 if (array != null) array[i] = value; 722 } 723 addLong(long[] array, int i, long value)724 private static void addLong(long[] array, int i, long value) { 725 if (array != null) array[i] += value; 726 } 727 estimateResizeBuckets(long newBucketDuration)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 readFullLongArray(DataInputStream in)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 */ readVarLong(DataInputStream in)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 */ writeVarLong(DataOutputStream out, long value)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 readVarLongArray(DataInputStream in)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 writeVarLongArray(DataOutputStream out, long[] values, int size)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 { readLongArray(Parcel in)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 writeLongArray(Parcel out, long[] values, int size)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