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