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