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