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