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