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