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