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