OSDN Git Service

Make ConnectivityMetricsLogger and related classes @SystemApi
[android-x86/frameworks-base.git] / core / java / android / net / NetworkStatsHistory.java
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package android.net;
18
19 import static android.net.NetworkStats.IFACE_ALL;
20 import static android.net.NetworkStats.SET_DEFAULT;
21 import static android.net.NetworkStats.TAG_NONE;
22 import static android.net.NetworkStats.UID_ALL;
23 import static android.net.NetworkStatsHistory.DataStreamUtils.readFullLongArray;
24 import static android.net.NetworkStatsHistory.DataStreamUtils.readVarLongArray;
25 import static android.net.NetworkStatsHistory.DataStreamUtils.writeVarLongArray;
26 import static android.net.NetworkStatsHistory.Entry.UNKNOWN;
27 import static android.net.NetworkStatsHistory.ParcelUtils.readLongArray;
28 import static android.net.NetworkStatsHistory.ParcelUtils.writeLongArray;
29 import static android.text.format.DateUtils.SECOND_IN_MILLIS;
30 import static com.android.internal.util.ArrayUtils.total;
31
32 import android.os.Parcel;
33 import android.os.Parcelable;
34 import android.util.MathUtils;
35
36 import com.android.internal.util.IndentingPrintWriter;
37
38 import java.io.CharArrayWriter;
39 import java.io.DataInputStream;
40 import java.io.DataOutputStream;
41 import java.io.IOException;
42 import java.io.PrintWriter;
43 import java.net.ProtocolException;
44 import java.util.Arrays;
45 import 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  */
58 public 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 }