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.annotation.SystemApi.Client.MODULE_LIBRARIES; 20 import static android.net.NetworkStats.IFACE_ALL; 21 import static android.net.NetworkStats.SET_DEFAULT; 22 import static android.net.NetworkStats.TAG_NONE; 23 import static android.net.NetworkStats.UID_ALL; 24 import static android.net.NetworkStatsHistory.DataStreamUtils.readFullLongArray; 25 import static android.net.NetworkStatsHistory.DataStreamUtils.readVarLongArray; 26 import static android.net.NetworkStatsHistory.DataStreamUtils.writeVarLongArray; 27 import static android.net.NetworkStatsHistory.Entry.UNKNOWN; 28 import static android.net.NetworkStatsHistory.ParcelUtils.readLongArray; 29 import static android.net.NetworkStatsHistory.ParcelUtils.writeLongArray; 30 import static android.text.format.DateUtils.SECOND_IN_MILLIS; 31 32 import static com.android.net.module.util.NetworkStatsUtils.multiplySafeByRational; 33 34 import android.annotation.NonNull; 35 import android.annotation.Nullable; 36 import android.annotation.SystemApi; 37 import android.compat.annotation.UnsupportedAppUsage; 38 import android.os.Build; 39 import android.os.Parcel; 40 import android.os.Parcelable; 41 import android.service.NetworkStatsHistoryBucketProto; 42 import android.service.NetworkStatsHistoryProto; 43 import android.util.IndentingPrintWriter; 44 import android.util.proto.ProtoOutputStream; 45 46 import com.android.net.module.util.CollectionUtils; 47 import com.android.net.module.util.NetworkStatsUtils; 48 49 import libcore.util.EmptyArray; 50 51 import java.io.CharArrayWriter; 52 import java.io.DataInput; 53 import java.io.DataOutput; 54 import java.io.IOException; 55 import java.io.PrintWriter; 56 import java.net.ProtocolException; 57 import java.util.ArrayList; 58 import java.util.Arrays; 59 import java.util.List; 60 import java.util.Random; 61 import java.util.TreeMap; 62 63 /** 64 * Collection of historical network statistics, recorded into equally-sized 65 * "buckets" in time. Internally it stores data in {@code long} series for more 66 * efficient persistence. 67 * <p> 68 * Each bucket is defined by a {@link #bucketStart} timestamp, and lasts for 69 * {@link #bucketDuration}. Internally assumes that {@link #bucketStart} is 70 * sorted at all times. 71 * 72 * @hide 73 */ 74 @SystemApi(client = MODULE_LIBRARIES) 75 public final class NetworkStatsHistory implements Parcelable { 76 private static final int VERSION_INIT = 1; 77 private static final int VERSION_ADD_PACKETS = 2; 78 private static final int VERSION_ADD_ACTIVE = 3; 79 80 /** @hide */ 81 public static final int FIELD_ACTIVE_TIME = 0x01; 82 /** @hide */ 83 public static final int FIELD_RX_BYTES = 0x02; 84 /** @hide */ 85 public static final int FIELD_RX_PACKETS = 0x04; 86 /** @hide */ 87 public static final int FIELD_TX_BYTES = 0x08; 88 /** @hide */ 89 public static final int FIELD_TX_PACKETS = 0x10; 90 /** @hide */ 91 public static final int FIELD_OPERATIONS = 0x20; 92 /** @hide */ 93 public static final int FIELD_ALL = 0xFFFFFFFF; 94 95 private long bucketDuration; 96 private int bucketCount; 97 private long[] bucketStart; 98 private long[] activeTime; 99 private long[] rxBytes; 100 private long[] rxPackets; 101 private long[] txBytes; 102 private long[] txPackets; 103 private long[] operations; 104 private long totalBytes; 105 106 /** @hide */ NetworkStatsHistory(long bucketDuration, long[] bucketStart, long[] activeTime, long[] rxBytes, long[] rxPackets, long[] txBytes, long[] txPackets, long[] operations, int bucketCount, long totalBytes)107 public NetworkStatsHistory(long bucketDuration, long[] bucketStart, long[] activeTime, 108 long[] rxBytes, long[] rxPackets, long[] txBytes, long[] txPackets, 109 long[] operations, int bucketCount, long totalBytes) { 110 this.bucketDuration = bucketDuration; 111 this.bucketStart = bucketStart; 112 this.activeTime = activeTime; 113 this.rxBytes = rxBytes; 114 this.rxPackets = rxPackets; 115 this.txBytes = txBytes; 116 this.txPackets = txPackets; 117 this.operations = operations; 118 this.bucketCount = bucketCount; 119 this.totalBytes = totalBytes; 120 } 121 122 /** 123 * An instance to represent a single record in a {@link NetworkStatsHistory} object. 124 */ 125 public static final class Entry { 126 /** @hide */ 127 public static final long UNKNOWN = -1; 128 129 /** @hide */ 130 // TODO: Migrate all callers to get duration from the history object and remove this field. 131 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 132 public long bucketDuration; 133 /** @hide */ 134 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 135 public long bucketStart; 136 /** @hide */ 137 public long activeTime; 138 /** @hide */ 139 @UnsupportedAppUsage 140 public long rxBytes; 141 /** @hide */ 142 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 143 public long rxPackets; 144 /** @hide */ 145 @UnsupportedAppUsage 146 public long txBytes; 147 /** @hide */ 148 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 149 public long txPackets; 150 /** @hide */ 151 public long operations; 152 /** @hide */ Entry()153 Entry() {} 154 155 /** 156 * Construct a {@link Entry} instance to represent a single record in a 157 * {@link NetworkStatsHistory} object. 158 * 159 * @param bucketStart Start of period for this {@link Entry}, in milliseconds since the 160 * Unix epoch, see {@link java.lang.System#currentTimeMillis}. 161 * @param activeTime Active time for this {@link Entry}, in milliseconds. 162 * @param rxBytes Number of bytes received for this {@link Entry}. Statistics should 163 * represent the contents of IP packets, including IP headers. 164 * @param rxPackets Number of packets received for this {@link Entry}. Statistics should 165 * represent the contents of IP packets, including IP headers. 166 * @param txBytes Number of bytes transmitted for this {@link Entry}. Statistics should 167 * represent the contents of IP packets, including IP headers. 168 * @param txPackets Number of bytes transmitted for this {@link Entry}. Statistics should 169 * represent the contents of IP packets, including IP headers. 170 * @param operations count of network operations performed for this {@link Entry}. This can 171 * be used to derive bytes-per-operation. 172 */ Entry(long bucketStart, long activeTime, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)173 public Entry(long bucketStart, long activeTime, long rxBytes, 174 long rxPackets, long txBytes, long txPackets, long operations) { 175 this.bucketStart = bucketStart; 176 this.activeTime = activeTime; 177 this.rxBytes = rxBytes; 178 this.rxPackets = rxPackets; 179 this.txBytes = txBytes; 180 this.txPackets = txPackets; 181 this.operations = operations; 182 } 183 184 /** 185 * Get start timestamp of the bucket's time interval, in milliseconds since the Unix epoch. 186 */ getBucketStart()187 public long getBucketStart() { 188 return bucketStart; 189 } 190 191 /** 192 * Get active time of the bucket's time interval, in milliseconds. 193 */ getActiveTime()194 public long getActiveTime() { 195 return activeTime; 196 } 197 198 /** Get number of bytes received for this {@link Entry}. */ getRxBytes()199 public long getRxBytes() { 200 return rxBytes; 201 } 202 203 /** Get number of packets received for this {@link Entry}. */ getRxPackets()204 public long getRxPackets() { 205 return rxPackets; 206 } 207 208 /** Get number of bytes transmitted for this {@link Entry}. */ getTxBytes()209 public long getTxBytes() { 210 return txBytes; 211 } 212 213 /** Get number of packets transmitted for this {@link Entry}. */ getTxPackets()214 public long getTxPackets() { 215 return txPackets; 216 } 217 218 /** Get count of network operations performed for this {@link Entry}. */ getOperations()219 public long getOperations() { 220 return operations; 221 } 222 223 @Override equals(Object o)224 public boolean equals(Object o) { 225 if (this == o) return true; 226 if (o.getClass() != getClass()) return false; 227 Entry entry = (Entry) o; 228 return bucketStart == entry.bucketStart 229 && activeTime == entry.activeTime && rxBytes == entry.rxBytes 230 && rxPackets == entry.rxPackets && txBytes == entry.txBytes 231 && txPackets == entry.txPackets && operations == entry.operations; 232 } 233 234 @Override hashCode()235 public int hashCode() { 236 return (int) (bucketStart * 2 237 + activeTime * 3 238 + rxBytes * 5 239 + rxPackets * 7 240 + txBytes * 11 241 + txPackets * 13 242 + operations * 17); 243 } 244 245 @Override toString()246 public String toString() { 247 return "Entry{" 248 + "bucketStart=" + bucketStart 249 + ", activeTime=" + activeTime 250 + ", rxBytes=" + rxBytes 251 + ", rxPackets=" + rxPackets 252 + ", txBytes=" + txBytes 253 + ", txPackets=" + txPackets 254 + ", operations=" + operations 255 + "}"; 256 } 257 258 /** 259 * Add the given {@link Entry} with this instance and return a new {@link Entry} 260 * instance as the result. 261 * 262 * @hide 263 */ 264 @NonNull plus(@onNull Entry another, long bucketDuration)265 public Entry plus(@NonNull Entry another, long bucketDuration) { 266 if (this.bucketStart != another.bucketStart) { 267 throw new IllegalArgumentException("bucketStart " + this.bucketStart 268 + " is not equal to " + another.bucketStart); 269 } 270 return new Entry(this.bucketStart, 271 // Active time should not go over bucket duration. 272 Math.min(this.activeTime + another.activeTime, bucketDuration), 273 this.rxBytes + another.rxBytes, 274 this.rxPackets + another.rxPackets, 275 this.txBytes + another.txBytes, 276 this.txPackets + another.txPackets, 277 this.operations + another.operations); 278 } 279 } 280 281 /** @hide */ 282 @UnsupportedAppUsage NetworkStatsHistory(long bucketDuration)283 public NetworkStatsHistory(long bucketDuration) { 284 this(bucketDuration, 10, FIELD_ALL); 285 } 286 287 /** @hide */ NetworkStatsHistory(long bucketDuration, int initialSize)288 public NetworkStatsHistory(long bucketDuration, int initialSize) { 289 this(bucketDuration, initialSize, FIELD_ALL); 290 } 291 292 /** @hide */ NetworkStatsHistory(long bucketDuration, int initialSize, int fields)293 public NetworkStatsHistory(long bucketDuration, int initialSize, int fields) { 294 this.bucketDuration = bucketDuration; 295 bucketStart = new long[initialSize]; 296 if ((fields & FIELD_ACTIVE_TIME) != 0) activeTime = new long[initialSize]; 297 if ((fields & FIELD_RX_BYTES) != 0) rxBytes = new long[initialSize]; 298 if ((fields & FIELD_RX_PACKETS) != 0) rxPackets = new long[initialSize]; 299 if ((fields & FIELD_TX_BYTES) != 0) txBytes = new long[initialSize]; 300 if ((fields & FIELD_TX_PACKETS) != 0) txPackets = new long[initialSize]; 301 if ((fields & FIELD_OPERATIONS) != 0) operations = new long[initialSize]; 302 bucketCount = 0; 303 totalBytes = 0; 304 } 305 306 /** @hide */ NetworkStatsHistory(NetworkStatsHistory existing, long bucketDuration)307 public NetworkStatsHistory(NetworkStatsHistory existing, long bucketDuration) { 308 this(bucketDuration, existing.estimateResizeBuckets(bucketDuration)); 309 recordEntireHistory(existing); 310 } 311 312 /** @hide */ 313 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) NetworkStatsHistory(Parcel in)314 public NetworkStatsHistory(Parcel in) { 315 bucketDuration = in.readLong(); 316 bucketStart = readLongArray(in); 317 activeTime = readLongArray(in); 318 rxBytes = readLongArray(in); 319 rxPackets = readLongArray(in); 320 txBytes = readLongArray(in); 321 txPackets = readLongArray(in); 322 operations = readLongArray(in); 323 bucketCount = bucketStart.length; 324 totalBytes = in.readLong(); 325 } 326 327 @Override writeToParcel(@onNull Parcel out, int flags)328 public void writeToParcel(@NonNull Parcel out, int flags) { 329 out.writeLong(bucketDuration); 330 writeLongArray(out, bucketStart, bucketCount); 331 writeLongArray(out, activeTime, bucketCount); 332 writeLongArray(out, rxBytes, bucketCount); 333 writeLongArray(out, rxPackets, bucketCount); 334 writeLongArray(out, txBytes, bucketCount); 335 writeLongArray(out, txPackets, bucketCount); 336 writeLongArray(out, operations, bucketCount); 337 out.writeLong(totalBytes); 338 } 339 340 /** @hide */ NetworkStatsHistory(DataInput in)341 public NetworkStatsHistory(DataInput in) throws IOException { 342 final int version = in.readInt(); 343 switch (version) { 344 case VERSION_INIT: { 345 bucketDuration = in.readLong(); 346 bucketStart = readFullLongArray(in); 347 rxBytes = readFullLongArray(in); 348 rxPackets = new long[bucketStart.length]; 349 txBytes = readFullLongArray(in); 350 txPackets = new long[bucketStart.length]; 351 operations = new long[bucketStart.length]; 352 bucketCount = bucketStart.length; 353 totalBytes = CollectionUtils.total(rxBytes) + CollectionUtils.total(txBytes); 354 break; 355 } 356 case VERSION_ADD_PACKETS: 357 case VERSION_ADD_ACTIVE: { 358 bucketDuration = in.readLong(); 359 bucketStart = readVarLongArray(in); 360 activeTime = (version >= VERSION_ADD_ACTIVE) ? readVarLongArray(in) 361 : new long[bucketStart.length]; 362 rxBytes = readVarLongArray(in); 363 rxPackets = readVarLongArray(in); 364 txBytes = readVarLongArray(in); 365 txPackets = readVarLongArray(in); 366 operations = readVarLongArray(in); 367 bucketCount = bucketStart.length; 368 totalBytes = CollectionUtils.total(rxBytes) + CollectionUtils.total(txBytes); 369 break; 370 } 371 default: { 372 throw new ProtocolException("unexpected version: " + version); 373 } 374 } 375 376 if (bucketStart.length != bucketCount || rxBytes.length != bucketCount 377 || rxPackets.length != bucketCount || txBytes.length != bucketCount 378 || txPackets.length != bucketCount || operations.length != bucketCount) { 379 throw new ProtocolException("Mismatched history lengths"); 380 } 381 } 382 383 /** @hide */ writeToStream(DataOutput out)384 public void writeToStream(DataOutput out) throws IOException { 385 out.writeInt(VERSION_ADD_ACTIVE); 386 out.writeLong(bucketDuration); 387 writeVarLongArray(out, bucketStart, bucketCount); 388 writeVarLongArray(out, activeTime, bucketCount); 389 writeVarLongArray(out, rxBytes, bucketCount); 390 writeVarLongArray(out, rxPackets, bucketCount); 391 writeVarLongArray(out, txBytes, bucketCount); 392 writeVarLongArray(out, txPackets, bucketCount); 393 writeVarLongArray(out, operations, bucketCount); 394 } 395 396 @Override describeContents()397 public int describeContents() { 398 return 0; 399 } 400 401 /** @hide */ 402 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) size()403 public int size() { 404 return bucketCount; 405 } 406 407 /** @hide */ getBucketDuration()408 public long getBucketDuration() { 409 return bucketDuration; 410 } 411 412 /** @hide */ 413 @UnsupportedAppUsage getStart()414 public long getStart() { 415 if (bucketCount > 0) { 416 return bucketStart[0]; 417 } else { 418 return Long.MAX_VALUE; 419 } 420 } 421 422 /** @hide */ 423 @UnsupportedAppUsage getEnd()424 public long getEnd() { 425 if (bucketCount > 0) { 426 return bucketStart[bucketCount - 1] + bucketDuration; 427 } else { 428 return Long.MIN_VALUE; 429 } 430 } 431 432 /** 433 * Return total bytes represented by this history. 434 * @hide 435 */ getTotalBytes()436 public long getTotalBytes() { 437 return totalBytes; 438 } 439 440 /** 441 * Return index of bucket that contains or is immediately before the 442 * requested time. 443 * @hide 444 */ 445 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getIndexBefore(long time)446 public int getIndexBefore(long time) { 447 int index = Arrays.binarySearch(bucketStart, 0, bucketCount, time); 448 if (index < 0) { 449 index = (~index) - 1; 450 } else { 451 index -= 1; 452 } 453 return NetworkStatsUtils.constrain(index, 0, bucketCount - 1); 454 } 455 456 /** 457 * Return index of bucket that contains or is immediately after the 458 * requested time. 459 * @hide 460 */ getIndexAfter(long time)461 public int getIndexAfter(long time) { 462 int index = Arrays.binarySearch(bucketStart, 0, bucketCount, time); 463 if (index < 0) { 464 index = ~index; 465 } else { 466 index += 1; 467 } 468 return NetworkStatsUtils.constrain(index, 0, bucketCount - 1); 469 } 470 471 /** 472 * Return specific stats entry. 473 * @hide 474 */ 475 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getValues(int i, Entry recycle)476 public Entry getValues(int i, Entry recycle) { 477 final Entry entry = recycle != null ? recycle : new Entry(); 478 entry.bucketStart = bucketStart[i]; 479 entry.bucketDuration = bucketDuration; 480 entry.activeTime = getLong(activeTime, i, UNKNOWN); 481 entry.rxBytes = getLong(rxBytes, i, UNKNOWN); 482 entry.rxPackets = getLong(rxPackets, i, UNKNOWN); 483 entry.txBytes = getLong(txBytes, i, UNKNOWN); 484 entry.txPackets = getLong(txPackets, i, UNKNOWN); 485 entry.operations = getLong(operations, i, UNKNOWN); 486 return entry; 487 } 488 489 /** 490 * Get List of {@link Entry} of the {@link NetworkStatsHistory} instance. 491 * 492 * @return 493 */ 494 @NonNull getEntries()495 public List<Entry> getEntries() { 496 // TODO: Return a wrapper that uses this list instead, to prevent the returned result 497 // from being changed. 498 final ArrayList<Entry> ret = new ArrayList<>(size()); 499 for (int i = 0; i < size(); i++) { 500 ret.add(getValues(i, null /* recycle */)); 501 } 502 return ret; 503 } 504 505 /** @hide */ setValues(int i, Entry entry)506 public void setValues(int i, Entry entry) { 507 // Unwind old values 508 if (rxBytes != null) totalBytes -= rxBytes[i]; 509 if (txBytes != null) totalBytes -= txBytes[i]; 510 511 bucketStart[i] = entry.bucketStart; 512 setLong(activeTime, i, entry.activeTime); 513 setLong(rxBytes, i, entry.rxBytes); 514 setLong(rxPackets, i, entry.rxPackets); 515 setLong(txBytes, i, entry.txBytes); 516 setLong(txPackets, i, entry.txPackets); 517 setLong(operations, i, entry.operations); 518 519 // Apply new values 520 if (rxBytes != null) totalBytes += rxBytes[i]; 521 if (txBytes != null) totalBytes += txBytes[i]; 522 } 523 524 /** 525 * Record that data traffic occurred in the given time range. Will 526 * distribute across internal buckets, creating new buckets as needed. 527 * @hide 528 */ 529 @Deprecated recordData(long start, long end, long rxBytes, long txBytes)530 public void recordData(long start, long end, long rxBytes, long txBytes) { 531 recordData(start, end, new NetworkStats.Entry( 532 IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, 0L, txBytes, 0L, 0L)); 533 } 534 535 /** 536 * Record that data traffic occurred in the given time range. Will 537 * distribute across internal buckets, creating new buckets as needed. 538 * @hide 539 */ recordData(long start, long end, NetworkStats.Entry entry)540 public void recordData(long start, long end, NetworkStats.Entry entry) { 541 long rxBytes = entry.rxBytes; 542 long rxPackets = entry.rxPackets; 543 long txBytes = entry.txBytes; 544 long txPackets = entry.txPackets; 545 long operations = entry.operations; 546 547 if (entry.isNegative()) { 548 throw new IllegalArgumentException("tried recording negative data"); 549 } 550 if (entry.isEmpty()) { 551 return; 552 } 553 554 // create any buckets needed by this range 555 ensureBuckets(start, end); 556 // Return fast if there is still no entry. This would typically happen when the start, 557 // end or duration are not valid values, e.g. start > end, negative duration value, etc. 558 if (bucketCount == 0) return; 559 560 // distribute data usage into buckets 561 long duration = end - start; 562 final int startIndex = getIndexAfter(end); 563 for (int i = startIndex; i >= 0; i--) { 564 final long curStart = bucketStart[i]; 565 final long curEnd = curStart + bucketDuration; 566 567 // bucket is older than record; we're finished 568 if (curEnd < start) break; 569 // bucket is newer than record; keep looking 570 if (curStart > end) continue; 571 572 final long overlap = Math.min(curEnd, end) - Math.max(curStart, start); 573 if (overlap <= 0) continue; 574 575 // integer math each time is faster than floating point 576 final long fracRxBytes = multiplySafeByRational(rxBytes, overlap, duration); 577 final long fracRxPackets = multiplySafeByRational(rxPackets, overlap, duration); 578 final long fracTxBytes = multiplySafeByRational(txBytes, overlap, duration); 579 final long fracTxPackets = multiplySafeByRational(txPackets, overlap, duration); 580 final long fracOperations = multiplySafeByRational(operations, overlap, duration); 581 582 583 addLong(activeTime, i, overlap); 584 addLong(this.rxBytes, i, fracRxBytes); rxBytes -= fracRxBytes; 585 addLong(this.rxPackets, i, fracRxPackets); rxPackets -= fracRxPackets; 586 addLong(this.txBytes, i, fracTxBytes); txBytes -= fracTxBytes; 587 addLong(this.txPackets, i, fracTxPackets); txPackets -= fracTxPackets; 588 addLong(this.operations, i, fracOperations); operations -= fracOperations; 589 590 duration -= overlap; 591 } 592 593 totalBytes += entry.rxBytes + entry.txBytes; 594 } 595 596 /** 597 * Record an entire {@link NetworkStatsHistory} into this history. Usually 598 * for combining together stats for external reporting. 599 * @hide 600 */ 601 @UnsupportedAppUsage recordEntireHistory(NetworkStatsHistory input)602 public void recordEntireHistory(NetworkStatsHistory input) { 603 recordHistory(input, Long.MIN_VALUE, Long.MAX_VALUE); 604 } 605 606 /** 607 * Record given {@link NetworkStatsHistory} into this history, copying only 608 * buckets that atomically occur in the inclusive time range. Doesn't 609 * interpolate across partial buckets. 610 * @hide 611 */ recordHistory(NetworkStatsHistory input, long start, long end)612 public void recordHistory(NetworkStatsHistory input, long start, long end) { 613 final NetworkStats.Entry entry = new NetworkStats.Entry( 614 IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L); 615 for (int i = 0; i < input.bucketCount; i++) { 616 final long bucketStart = input.bucketStart[i]; 617 final long bucketEnd = bucketStart + input.bucketDuration; 618 619 // skip when bucket is outside requested range 620 if (bucketStart < start || bucketEnd > end) continue; 621 622 entry.rxBytes = getLong(input.rxBytes, i, 0L); 623 entry.rxPackets = getLong(input.rxPackets, i, 0L); 624 entry.txBytes = getLong(input.txBytes, i, 0L); 625 entry.txPackets = getLong(input.txPackets, i, 0L); 626 entry.operations = getLong(input.operations, i, 0L); 627 628 recordData(bucketStart, bucketEnd, entry); 629 } 630 } 631 632 /** 633 * Ensure that buckets exist for given time range, creating as needed. 634 */ ensureBuckets(long start, long end)635 private void ensureBuckets(long start, long end) { 636 // normalize incoming range to bucket boundaries 637 start -= start % bucketDuration; 638 end += (bucketDuration - (end % bucketDuration)) % bucketDuration; 639 640 for (long now = start; now < end; now += bucketDuration) { 641 // try finding existing bucket 642 final int index = Arrays.binarySearch(bucketStart, 0, bucketCount, now); 643 if (index < 0) { 644 // bucket missing, create and insert 645 insertBucket(~index, now); 646 } 647 } 648 } 649 650 /** 651 * Insert new bucket at requested index and starting time. 652 */ insertBucket(int index, long start)653 private void insertBucket(int index, long start) { 654 // create more buckets when needed 655 if (bucketCount >= bucketStart.length) { 656 final int newLength = Math.max(bucketStart.length, 10) * 3 / 2; 657 bucketStart = Arrays.copyOf(bucketStart, newLength); 658 if (activeTime != null) activeTime = Arrays.copyOf(activeTime, newLength); 659 if (rxBytes != null) rxBytes = Arrays.copyOf(rxBytes, newLength); 660 if (rxPackets != null) rxPackets = Arrays.copyOf(rxPackets, newLength); 661 if (txBytes != null) txBytes = Arrays.copyOf(txBytes, newLength); 662 if (txPackets != null) txPackets = Arrays.copyOf(txPackets, newLength); 663 if (operations != null) operations = Arrays.copyOf(operations, newLength); 664 } 665 666 // create gap when inserting bucket in middle 667 if (index < bucketCount) { 668 final int dstPos = index + 1; 669 final int length = bucketCount - index; 670 671 System.arraycopy(bucketStart, index, bucketStart, dstPos, length); 672 if (activeTime != null) System.arraycopy(activeTime, index, activeTime, dstPos, length); 673 if (rxBytes != null) System.arraycopy(rxBytes, index, rxBytes, dstPos, length); 674 if (rxPackets != null) System.arraycopy(rxPackets, index, rxPackets, dstPos, length); 675 if (txBytes != null) System.arraycopy(txBytes, index, txBytes, dstPos, length); 676 if (txPackets != null) System.arraycopy(txPackets, index, txPackets, dstPos, length); 677 if (operations != null) System.arraycopy(operations, index, operations, dstPos, length); 678 } 679 680 bucketStart[index] = start; 681 setLong(activeTime, index, 0L); 682 setLong(rxBytes, index, 0L); 683 setLong(rxPackets, index, 0L); 684 setLong(txBytes, index, 0L); 685 setLong(txPackets, index, 0L); 686 setLong(operations, index, 0L); 687 bucketCount++; 688 } 689 690 /** 691 * Clear all data stored in this object. 692 * @hide 693 */ clear()694 public void clear() { 695 bucketStart = EmptyArray.LONG; 696 if (activeTime != null) activeTime = EmptyArray.LONG; 697 if (rxBytes != null) rxBytes = EmptyArray.LONG; 698 if (rxPackets != null) rxPackets = EmptyArray.LONG; 699 if (txBytes != null) txBytes = EmptyArray.LONG; 700 if (txPackets != null) txPackets = EmptyArray.LONG; 701 if (operations != null) operations = EmptyArray.LONG; 702 bucketCount = 0; 703 totalBytes = 0; 704 } 705 706 /** 707 * Remove buckets that start older than requested cutoff. 708 * 709 * This method will remove any bucket that contains any data older than the requested 710 * cutoff, even if that same bucket includes some data from after the cutoff. 711 * 712 * @hide 713 */ removeBucketsStartingBefore(final long cutoff)714 public void removeBucketsStartingBefore(final long cutoff) { 715 // TODO: Consider use getIndexBefore. 716 int i; 717 for (i = 0; i < bucketCount; i++) { 718 final long curStart = bucketStart[i]; 719 720 // This bucket starts after or at the cutoff, so it should be kept. 721 if (curStart >= cutoff) break; 722 } 723 724 if (i > 0) { 725 final int length = bucketStart.length; 726 bucketStart = Arrays.copyOfRange(bucketStart, i, length); 727 if (activeTime != null) activeTime = Arrays.copyOfRange(activeTime, i, length); 728 if (rxBytes != null) rxBytes = Arrays.copyOfRange(rxBytes, i, length); 729 if (rxPackets != null) rxPackets = Arrays.copyOfRange(rxPackets, i, length); 730 if (txBytes != null) txBytes = Arrays.copyOfRange(txBytes, i, length); 731 if (txPackets != null) txPackets = Arrays.copyOfRange(txPackets, i, length); 732 if (operations != null) operations = Arrays.copyOfRange(operations, i, length); 733 bucketCount -= i; 734 735 totalBytes = 0; 736 if (rxBytes != null) totalBytes += CollectionUtils.total(rxBytes); 737 if (txBytes != null) totalBytes += CollectionUtils.total(txBytes); 738 } 739 } 740 741 /** 742 * Return interpolated data usage across the requested range. Interpolates 743 * across buckets, so values may be rounded slightly. 744 * 745 * <p>If the active bucket is not completed yet, it returns the proportional value of it 746 * based on its duration and the {@code end} param. 747 * 748 * @param start - start of the range, timestamp in milliseconds since the epoch. 749 * @param end - end of the range, timestamp in milliseconds since the epoch. 750 * @param recycle - entry instance for performance, could be null. 751 * @hide 752 */ 753 @UnsupportedAppUsage getValues(long start, long end, Entry recycle)754 public Entry getValues(long start, long end, Entry recycle) { 755 return getValues(start, end, Long.MAX_VALUE, recycle); 756 } 757 758 /** 759 * Return interpolated data usage across the requested range. Interpolates 760 * across buckets, so values may be rounded slightly. 761 * 762 * @param start - start of the range, timestamp in milliseconds since the epoch. 763 * @param end - end of the range, timestamp in milliseconds since the epoch. 764 * @param now - current timestamp in milliseconds since the epoch (wall clock). 765 * @param recycle - entry instance for performance, could be null. 766 * @hide 767 */ 768 @UnsupportedAppUsage getValues(long start, long end, long now, Entry recycle)769 public Entry getValues(long start, long end, long now, Entry recycle) { 770 final Entry entry = recycle != null ? recycle : new Entry(); 771 entry.bucketDuration = end - start; 772 entry.bucketStart = start; 773 entry.activeTime = activeTime != null ? 0 : UNKNOWN; 774 entry.rxBytes = rxBytes != null ? 0 : UNKNOWN; 775 entry.rxPackets = rxPackets != null ? 0 : UNKNOWN; 776 entry.txBytes = txBytes != null ? 0 : UNKNOWN; 777 entry.txPackets = txPackets != null ? 0 : UNKNOWN; 778 entry.operations = operations != null ? 0 : UNKNOWN; 779 780 // Return fast if there is no entry. 781 if (bucketCount == 0) return entry; 782 783 final int startIndex = getIndexAfter(end); 784 for (int i = startIndex; i >= 0; i--) { 785 final long curStart = bucketStart[i]; 786 long curEnd = curStart + bucketDuration; 787 788 // bucket is older than request; we're finished 789 if (curEnd <= start) break; 790 // bucket is newer than request; keep looking 791 if (curStart >= end) continue; 792 793 // the active bucket is shorter then a normal completed bucket 794 if (curEnd > now) curEnd = now; 795 // usually this is simply bucketDuration 796 final long bucketSpan = curEnd - curStart; 797 // prevent division by zero 798 if (bucketSpan <= 0) continue; 799 800 final long overlapEnd = curEnd < end ? curEnd : end; 801 final long overlapStart = curStart > start ? curStart : start; 802 final long overlap = overlapEnd - overlapStart; 803 if (overlap <= 0) continue; 804 805 // integer math each time is faster than floating point 806 if (activeTime != null) { 807 entry.activeTime += multiplySafeByRational(activeTime[i], overlap, bucketSpan); 808 } 809 if (rxBytes != null) { 810 entry.rxBytes += multiplySafeByRational(rxBytes[i], overlap, bucketSpan); 811 } 812 if (rxPackets != null) { 813 entry.rxPackets += multiplySafeByRational(rxPackets[i], overlap, bucketSpan); 814 } 815 if (txBytes != null) { 816 entry.txBytes += multiplySafeByRational(txBytes[i], overlap, bucketSpan); 817 } 818 if (txPackets != null) { 819 entry.txPackets += multiplySafeByRational(txPackets[i], overlap, bucketSpan); 820 } 821 if (operations != null) { 822 entry.operations += multiplySafeByRational(operations[i], overlap, bucketSpan); 823 } 824 } 825 return entry; 826 } 827 828 /** 829 * @deprecated only for temporary testing 830 * @hide 831 */ 832 @Deprecated generateRandom(long start, long end, long bytes)833 public void generateRandom(long start, long end, long bytes) { 834 final Random r = new Random(); 835 836 final float fractionRx = r.nextFloat(); 837 final long rxBytes = (long) (bytes * fractionRx); 838 final long txBytes = (long) (bytes * (1 - fractionRx)); 839 840 final long rxPackets = rxBytes / 1024; 841 final long txPackets = txBytes / 1024; 842 final long operations = rxBytes / 2048; 843 844 generateRandom(start, end, rxBytes, rxPackets, txBytes, txPackets, operations, r); 845 } 846 847 /** 848 * @deprecated only for temporary testing 849 * @hide 850 */ 851 @Deprecated generateRandom(long start, long end, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations, Random r)852 public void generateRandom(long start, long end, long rxBytes, long rxPackets, long txBytes, 853 long txPackets, long operations, Random r) { 854 ensureBuckets(start, end); 855 856 final NetworkStats.Entry entry = new NetworkStats.Entry( 857 IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L); 858 while (rxBytes > 1024 || rxPackets > 128 || txBytes > 1024 || txPackets > 128 859 || operations > 32) { 860 final long curStart = randomLong(r, start, end); 861 final long curEnd = curStart + randomLong(r, 0, (end - curStart) / 2); 862 863 entry.rxBytes = randomLong(r, 0, rxBytes); 864 entry.rxPackets = randomLong(r, 0, rxPackets); 865 entry.txBytes = randomLong(r, 0, txBytes); 866 entry.txPackets = randomLong(r, 0, txPackets); 867 entry.operations = randomLong(r, 0, operations); 868 869 rxBytes -= entry.rxBytes; 870 rxPackets -= entry.rxPackets; 871 txBytes -= entry.txBytes; 872 txPackets -= entry.txPackets; 873 operations -= entry.operations; 874 875 recordData(curStart, curEnd, entry); 876 } 877 } 878 879 /** @hide */ randomLong(Random r, long start, long end)880 public static long randomLong(Random r, long start, long end) { 881 return (long) (start + (r.nextFloat() * (end - start))); 882 } 883 884 /** 885 * Quickly determine if this history intersects with given window. 886 * @hide 887 */ intersects(long start, long end)888 public boolean intersects(long start, long end) { 889 final long dataStart = getStart(); 890 final long dataEnd = getEnd(); 891 if (start >= dataStart && start <= dataEnd) return true; 892 if (end >= dataStart && end <= dataEnd) return true; 893 if (dataStart >= start && dataStart <= end) return true; 894 if (dataEnd >= start && dataEnd <= end) return true; 895 return false; 896 } 897 898 /** @hide */ dump(IndentingPrintWriter pw, boolean fullHistory)899 public void dump(IndentingPrintWriter pw, boolean fullHistory) { 900 pw.print("NetworkStatsHistory: bucketDuration="); 901 pw.println(bucketDuration / SECOND_IN_MILLIS); 902 pw.increaseIndent(); 903 904 final int start = fullHistory ? 0 : Math.max(0, bucketCount - 32); 905 if (start > 0) { 906 pw.print("(omitting "); pw.print(start); pw.println(" buckets)"); 907 } 908 909 for (int i = start; i < bucketCount; i++) { 910 pw.print("st="); pw.print(bucketStart[i] / SECOND_IN_MILLIS); 911 if (rxBytes != null) { pw.print(" rb="); pw.print(rxBytes[i]); } 912 if (rxPackets != null) { pw.print(" rp="); pw.print(rxPackets[i]); } 913 if (txBytes != null) { pw.print(" tb="); pw.print(txBytes[i]); } 914 if (txPackets != null) { pw.print(" tp="); pw.print(txPackets[i]); } 915 if (operations != null) { pw.print(" op="); pw.print(operations[i]); } 916 pw.println(); 917 } 918 919 pw.decreaseIndent(); 920 } 921 922 /** @hide */ dumpCheckin(PrintWriter pw)923 public void dumpCheckin(PrintWriter pw) { 924 pw.print("d,"); 925 pw.print(bucketDuration / SECOND_IN_MILLIS); 926 pw.println(); 927 928 for (int i = 0; i < bucketCount; i++) { 929 pw.print("b,"); 930 pw.print(bucketStart[i] / SECOND_IN_MILLIS); pw.print(','); 931 if (rxBytes != null) { pw.print(rxBytes[i]); } else { pw.print("*"); } pw.print(','); 932 if (rxPackets != null) { pw.print(rxPackets[i]); } else { pw.print("*"); } pw.print(','); 933 if (txBytes != null) { pw.print(txBytes[i]); } else { pw.print("*"); } pw.print(','); 934 if (txPackets != null) { pw.print(txPackets[i]); } else { pw.print("*"); } pw.print(','); 935 if (operations != null) { pw.print(operations[i]); } else { pw.print("*"); } 936 pw.println(); 937 } 938 } 939 940 /** @hide */ dumpDebug(ProtoOutputStream proto, long tag)941 public void dumpDebug(ProtoOutputStream proto, long tag) { 942 final long start = proto.start(tag); 943 944 proto.write(NetworkStatsHistoryProto.BUCKET_DURATION_MS, bucketDuration); 945 946 for (int i = 0; i < bucketCount; i++) { 947 final long startBucket = proto.start(NetworkStatsHistoryProto.BUCKETS); 948 949 proto.write(NetworkStatsHistoryBucketProto.BUCKET_START_MS, 950 bucketStart[i]); 951 dumpDebug(proto, NetworkStatsHistoryBucketProto.RX_BYTES, rxBytes, i); 952 dumpDebug(proto, NetworkStatsHistoryBucketProto.RX_PACKETS, rxPackets, i); 953 dumpDebug(proto, NetworkStatsHistoryBucketProto.TX_BYTES, txBytes, i); 954 dumpDebug(proto, NetworkStatsHistoryBucketProto.TX_PACKETS, txPackets, i); 955 dumpDebug(proto, NetworkStatsHistoryBucketProto.OPERATIONS, operations, i); 956 957 proto.end(startBucket); 958 } 959 960 proto.end(start); 961 } 962 dumpDebug(ProtoOutputStream proto, long tag, long[] array, int index)963 private static void dumpDebug(ProtoOutputStream proto, long tag, long[] array, int index) { 964 if (array != null) { 965 proto.write(tag, array[index]); 966 } 967 } 968 969 @Override toString()970 public String toString() { 971 final CharArrayWriter writer = new CharArrayWriter(); 972 dump(new IndentingPrintWriter(writer, " "), false); 973 return writer.toString(); 974 } 975 976 /** 977 * Same as "equals", but not actually called equals as this would affect public API behavior. 978 * @hide 979 */ 980 @Nullable isSameAs(NetworkStatsHistory other)981 public boolean isSameAs(NetworkStatsHistory other) { 982 return bucketCount == other.bucketCount 983 && Arrays.equals(bucketStart, other.bucketStart) 984 // Don't check activeTime since it can change on import due to the importer using 985 // recordHistory. It's also not exposed by the APIs or present in dumpsys or 986 // toString(). 987 && Arrays.equals(rxBytes, other.rxBytes) 988 && Arrays.equals(rxPackets, other.rxPackets) 989 && Arrays.equals(txBytes, other.txBytes) 990 && Arrays.equals(txPackets, other.txPackets) 991 && Arrays.equals(operations, other.operations) 992 && totalBytes == other.totalBytes; 993 } 994 995 @UnsupportedAppUsage 996 public static final @android.annotation.NonNull Creator<NetworkStatsHistory> CREATOR = new Creator<NetworkStatsHistory>() { 997 @Override 998 public NetworkStatsHistory createFromParcel(Parcel in) { 999 return new NetworkStatsHistory(in); 1000 } 1001 1002 @Override 1003 public NetworkStatsHistory[] newArray(int size) { 1004 return new NetworkStatsHistory[size]; 1005 } 1006 }; 1007 getLong(long[] array, int i, long value)1008 private static long getLong(long[] array, int i, long value) { 1009 return array != null ? array[i] : value; 1010 } 1011 setLong(long[] array, int i, long value)1012 private static void setLong(long[] array, int i, long value) { 1013 if (array != null) array[i] = value; 1014 } 1015 addLong(long[] array, int i, long value)1016 private static void addLong(long[] array, int i, long value) { 1017 if (array != null) array[i] += value; 1018 } 1019 1020 /** @hide */ estimateResizeBuckets(long newBucketDuration)1021 public int estimateResizeBuckets(long newBucketDuration) { 1022 return (int) (size() * getBucketDuration() / newBucketDuration); 1023 } 1024 1025 /** 1026 * Utility methods for interacting with {@link DataInputStream} and 1027 * {@link DataOutputStream}, mostly dealing with writing partial arrays. 1028 * @hide 1029 */ 1030 public static class DataStreamUtils { 1031 @Deprecated readFullLongArray(DataInput in)1032 public static long[] readFullLongArray(DataInput in) throws IOException { 1033 final int size = in.readInt(); 1034 if (size < 0) throw new ProtocolException("negative array size"); 1035 final long[] values = new long[size]; 1036 for (int i = 0; i < values.length; i++) { 1037 values[i] = in.readLong(); 1038 } 1039 return values; 1040 } 1041 1042 /** 1043 * Read variable-length {@link Long} using protobuf-style approach. 1044 */ readVarLong(DataInput in)1045 public static long readVarLong(DataInput in) throws IOException { 1046 int shift = 0; 1047 long result = 0; 1048 while (shift < 64) { 1049 byte b = in.readByte(); 1050 result |= (long) (b & 0x7F) << shift; 1051 if ((b & 0x80) == 0) 1052 return result; 1053 shift += 7; 1054 } 1055 throw new ProtocolException("malformed long"); 1056 } 1057 1058 /** 1059 * Write variable-length {@link Long} using protobuf-style approach. 1060 */ writeVarLong(DataOutput out, long value)1061 public static void writeVarLong(DataOutput out, long value) throws IOException { 1062 while (true) { 1063 if ((value & ~0x7FL) == 0) { 1064 out.writeByte((int) value); 1065 return; 1066 } else { 1067 out.writeByte(((int) value & 0x7F) | 0x80); 1068 value >>>= 7; 1069 } 1070 } 1071 } 1072 readVarLongArray(DataInput in)1073 public static long[] readVarLongArray(DataInput in) throws IOException { 1074 final int size = in.readInt(); 1075 if (size == -1) return null; 1076 if (size < 0) throw new ProtocolException("negative array size"); 1077 final long[] values = new long[size]; 1078 for (int i = 0; i < values.length; i++) { 1079 values[i] = readVarLong(in); 1080 } 1081 return values; 1082 } 1083 writeVarLongArray(DataOutput out, long[] values, int size)1084 public static void writeVarLongArray(DataOutput out, long[] values, int size) 1085 throws IOException { 1086 if (values == null) { 1087 out.writeInt(-1); 1088 return; 1089 } 1090 if (size > values.length) { 1091 throw new IllegalArgumentException("size larger than length"); 1092 } 1093 out.writeInt(size); 1094 for (int i = 0; i < size; i++) { 1095 writeVarLong(out, values[i]); 1096 } 1097 } 1098 } 1099 1100 /** 1101 * Utility methods for interacting with {@link Parcel} structures, mostly 1102 * dealing with writing partial arrays. 1103 * @hide 1104 */ 1105 public static class ParcelUtils { readLongArray(Parcel in)1106 public static long[] readLongArray(Parcel in) { 1107 final int size = in.readInt(); 1108 if (size == -1) return null; 1109 final long[] values = new long[size]; 1110 for (int i = 0; i < values.length; i++) { 1111 values[i] = in.readLong(); 1112 } 1113 return values; 1114 } 1115 writeLongArray(Parcel out, long[] values, int size)1116 public static void writeLongArray(Parcel out, long[] values, int size) { 1117 if (values == null) { 1118 out.writeInt(-1); 1119 return; 1120 } 1121 if (size > values.length) { 1122 throw new IllegalArgumentException("size larger than length"); 1123 } 1124 out.writeInt(size); 1125 for (int i = 0; i < size; i++) { 1126 out.writeLong(values[i]); 1127 } 1128 } 1129 } 1130 1131 /** 1132 * Builder class for {@link NetworkStatsHistory}. 1133 */ 1134 public static final class Builder { 1135 private final TreeMap<Long, Entry> mEntries; 1136 private final long mBucketDuration; 1137 1138 /** 1139 * Creates a new Builder with given bucket duration and initial capacity to construct 1140 * {@link NetworkStatsHistory} objects. 1141 * 1142 * @param bucketDuration Duration of the buckets of the object, in milliseconds. 1143 * @param initialCapacity Estimated number of records. 1144 */ Builder(long bucketDuration, int initialCapacity)1145 public Builder(long bucketDuration, int initialCapacity) { 1146 mBucketDuration = bucketDuration; 1147 // Create a collection that is always sorted and can deduplicate items by the timestamp. 1148 mEntries = new TreeMap<>(); 1149 } 1150 1151 /** 1152 * Add an {@link Entry} into the {@link NetworkStatsHistory} instance. If the timestamp 1153 * already exists, the given {@link Entry} will be combined into existing entry. 1154 * 1155 * @param entry The target {@link Entry} object. 1156 * @return The builder object. 1157 */ 1158 @NonNull addEntry(@onNull Entry entry)1159 public Builder addEntry(@NonNull Entry entry) { 1160 final Entry existing = mEntries.get(entry.bucketStart); 1161 if (existing != null) { 1162 mEntries.put(entry.bucketStart, existing.plus(entry, mBucketDuration)); 1163 } else { 1164 mEntries.put(entry.bucketStart, entry); 1165 } 1166 return this; 1167 } 1168 sum(@onNull long[] array)1169 private static long sum(@NonNull long[] array) { 1170 long sum = 0L; 1171 for (long entry : array) { 1172 sum += entry; 1173 } 1174 return sum; 1175 } 1176 1177 /** 1178 * Builds the instance of the {@link NetworkStatsHistory}. 1179 * 1180 * @return the built instance of {@link NetworkStatsHistory}. 1181 */ 1182 @NonNull build()1183 public NetworkStatsHistory build() { 1184 int size = mEntries.size(); 1185 final long[] bucketStart = new long[size]; 1186 final long[] activeTime = new long[size]; 1187 final long[] rxBytes = new long[size]; 1188 final long[] rxPackets = new long[size]; 1189 final long[] txBytes = new long[size]; 1190 final long[] txPackets = new long[size]; 1191 final long[] operations = new long[size]; 1192 1193 int i = 0; 1194 for (Entry entry : mEntries.values()) { 1195 bucketStart[i] = entry.bucketStart; 1196 activeTime[i] = entry.activeTime; 1197 rxBytes[i] = entry.rxBytes; 1198 rxPackets[i] = entry.rxPackets; 1199 txBytes[i] = entry.txBytes; 1200 txPackets[i] = entry.txPackets; 1201 operations[i] = entry.operations; 1202 i++; 1203 } 1204 1205 return new NetworkStatsHistory(mBucketDuration, bucketStart, activeTime, 1206 rxBytes, rxPackets, txBytes, txPackets, operations, 1207 size, sum(rxBytes) + sum(txBytes)); 1208 } 1209 } 1210 } 1211