• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
NetworkStatsHistory(long bucketDuration)96     public NetworkStatsHistory(long bucketDuration) {
97         this(bucketDuration, 10, FIELD_ALL);
98     }
99 
NetworkStatsHistory(long bucketDuration, int initialSize)100     public NetworkStatsHistory(long bucketDuration, int initialSize) {
101         this(bucketDuration, initialSize, FIELD_ALL);
102     }
103 
NetworkStatsHistory(long bucketDuration, int initialSize, int fields)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 
NetworkStatsHistory(NetworkStatsHistory existing, long bucketDuration)117     public NetworkStatsHistory(NetworkStatsHistory existing, long bucketDuration) {
118         this(bucketDuration, existing.estimateResizeBuckets(bucketDuration));
119         recordEntireHistory(existing);
120     }
121 
NetworkStatsHistory(Parcel in)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
writeToParcel(Parcel out, int flags)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 
NetworkStatsHistory(DataInputStream in)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 
writeToStream(DataOutputStream out)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
describeContents()203     public int describeContents() {
204         return 0;
205     }
206 
size()207     public int size() {
208         return bucketCount;
209     }
210 
getBucketDuration()211     public long getBucketDuration() {
212         return bucketDuration;
213     }
214 
getStart()215     public long getStart() {
216         if (bucketCount > 0) {
217             return bucketStart[0];
218         } else {
219             return Long.MAX_VALUE;
220         }
221     }
222 
getEnd()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      */
getTotalBytes()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      */
getIndexBefore(long time)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      */
getIndexAfter(long time)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      */
getValues(int i, Entry recycle)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
recordData(long start, long end, long rxBytes, long txBytes)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      */
recordData(long start, long end, NetworkStats.Entry entry)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      */
recordEntireHistory(NetworkStatsHistory input)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      */
recordHistory(NetworkStatsHistory input, long start, long end)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      */
ensureBuckets(long start, long end)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      */
insertBucket(int index, long start)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
removeBucketsBefore(long cutoff)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      */
getValues(long start, long end, Entry recycle)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      */
getValues(long start, long end, long now, Entry recycle)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
generateRandom(long start, long end, long bytes)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
generateRandom(long start, long end, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations, Random r)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 
randomLong(Random r, long start, long end)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      */
intersects(long start, long end)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 
dump(IndentingPrintWriter pw, boolean fullHistory)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 
dumpCheckin(PrintWriter pw)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
toString()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 
getLong(long[] array, int i, long value)650     private static long getLong(long[] array, int i, long value) {
651         return array != null ? array[i] : value;
652     }
653 
setLong(long[] array, int i, long value)654     private static void setLong(long[] array, int i, long value) {
655         if (array != null) array[i] = value;
656     }
657 
addLong(long[] array, int i, long value)658     private static void addLong(long[] array, int i, long value) {
659         if (array != null) array[i] += value;
660     }
661 
estimateResizeBuckets(long newBucketDuration)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
readFullLongArray(DataInputStream in)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          */
readVarLong(DataInputStream in)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          */
writeVarLong(DataOutputStream out, long value)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 
readVarLongArray(DataInputStream in)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 
writeVarLongArray(DataOutputStream out, long[] values, int size)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 {
readLongArray(Parcel in)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 
writeLongArray(Parcel out, long[] values, int size)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