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        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            final long[] values = new long[size];
643            for (int i = 0; i < values.length; i++) {
644                values[i] = in.readLong();
645            }
646            return values;
647        }
648
649        /**
650         * Read variable-length {@link Long} using protobuf-style approach.
651         */
652        public static long readVarLong(DataInputStream in) throws IOException {
653            int shift = 0;
654            long result = 0;
655            while (shift < 64) {
656                byte b = in.readByte();
657                result |= (long) (b & 0x7F) << shift;
658                if ((b & 0x80) == 0)
659                    return result;
660                shift += 7;
661            }
662            throw new ProtocolException("malformed long");
663        }
664
665        /**
666         * Write variable-length {@link Long} using protobuf-style approach.
667         */
668        public static void writeVarLong(DataOutputStream out, long value) throws IOException {
669            while (true) {
670                if ((value & ~0x7FL) == 0) {
671                    out.writeByte((int) value);
672                    return;
673                } else {
674                    out.writeByte(((int) value & 0x7F) | 0x80);
675                    value >>>= 7;
676                }
677            }
678        }
679
680        public static long[] readVarLongArray(DataInputStream in) throws IOException {
681            final int size = in.readInt();
682            if (size == -1) return null;
683            final long[] values = new long[size];
684            for (int i = 0; i < values.length; i++) {
685                values[i] = readVarLong(in);
686            }
687            return values;
688        }
689
690        public static void writeVarLongArray(DataOutputStream out, long[] values, int size)
691                throws IOException {
692            if (values == null) {
693                out.writeInt(-1);
694                return;
695            }
696            if (size > values.length) {
697                throw new IllegalArgumentException("size larger than length");
698            }
699            out.writeInt(size);
700            for (int i = 0; i < size; i++) {
701                writeVarLong(out, values[i]);
702            }
703        }
704    }
705
706    /**
707     * Utility methods for interacting with {@link Parcel} structures, mostly
708     * dealing with writing partial arrays.
709     */
710    public static class ParcelUtils {
711        public static long[] readLongArray(Parcel in) {
712            final int size = in.readInt();
713            if (size == -1) return null;
714            final long[] values = new long[size];
715            for (int i = 0; i < values.length; i++) {
716                values[i] = in.readLong();
717            }
718            return values;
719        }
720
721        public static void writeLongArray(Parcel out, long[] values, int size) {
722            if (values == null) {
723                out.writeInt(-1);
724                return;
725            }
726            if (size > values.length) {
727                throw new IllegalArgumentException("size larger than length");
728            }
729            out.writeInt(size);
730            for (int i = 0; i < size; i++) {
731                out.writeLong(values[i]);
732            }
733        }
734    }
735
736}
737