• 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.service.NetworkStatsHistoryBucketProto;
35 import android.service.NetworkStatsHistoryProto;
36 import android.util.MathUtils;
37 import android.util.proto.ProtoOutputStream;
38 
39 import com.android.internal.util.IndentingPrintWriter;
40 
41 import java.io.CharArrayWriter;
42 import java.io.DataInputStream;
43 import java.io.DataOutputStream;
44 import java.io.IOException;
45 import java.io.PrintWriter;
46 import java.net.ProtocolException;
47 import java.util.Arrays;
48 import 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  */
61 public 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 
NetworkStatsHistory(long bucketDuration)99     public NetworkStatsHistory(long bucketDuration) {
100         this(bucketDuration, 10, FIELD_ALL);
101     }
102 
NetworkStatsHistory(long bucketDuration, int initialSize)103     public NetworkStatsHistory(long bucketDuration, int initialSize) {
104         this(bucketDuration, initialSize, FIELD_ALL);
105     }
106 
NetworkStatsHistory(long bucketDuration, int initialSize, int fields)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 
NetworkStatsHistory(NetworkStatsHistory existing, long bucketDuration)120     public NetworkStatsHistory(NetworkStatsHistory existing, long bucketDuration) {
121         this(bucketDuration, existing.estimateResizeBuckets(bucketDuration));
122         recordEntireHistory(existing);
123     }
124 
NetworkStatsHistory(Parcel in)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
writeToParcel(Parcel out, int flags)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 
NetworkStatsHistory(DataInputStream in)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 
writeToStream(DataOutputStream out)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
describeContents()206     public int describeContents() {
207         return 0;
208     }
209 
size()210     public int size() {
211         return bucketCount;
212     }
213 
getBucketDuration()214     public long getBucketDuration() {
215         return bucketDuration;
216     }
217 
getStart()218     public long getStart() {
219         if (bucketCount > 0) {
220             return bucketStart[0];
221         } else {
222             return Long.MAX_VALUE;
223         }
224     }
225 
getEnd()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      */
getTotalBytes()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      */
getIndexBefore(long time)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      */
getIndexAfter(long time)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      */
getValues(int i, Entry recycle)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
recordData(long start, long end, long rxBytes, long txBytes)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      */
recordData(long start, long end, NetworkStats.Entry entry)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      */
recordEntireHistory(NetworkStatsHistory input)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      */
recordHistory(NetworkStatsHistory input, long start, long end)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      */
ensureBuckets(long start, long end)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      */
insertBucket(int index, long start)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
removeBucketsBefore(long cutoff)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      */
getValues(long start, long end, Entry recycle)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      */
getValues(long start, long end, long now, Entry recycle)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
generateRandom(long start, long end, long bytes)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
generateRandom(long start, long end, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations, Random r)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 
randomLong(Random r, long start, long end)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      */
intersects(long start, long end)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 
dump(IndentingPrintWriter pw, boolean fullHistory)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 
dumpCheckin(PrintWriter pw)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 
writeToProto(ProtoOutputStream proto, long tag)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 
writeToProto(ProtoOutputStream proto, long tag, long[] array, int index)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
toString()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 
getLong(long[] array, int i, long value)680     private static long getLong(long[] array, int i, long value) {
681         return array != null ? array[i] : value;
682     }
683 
setLong(long[] array, int i, long value)684     private static void setLong(long[] array, int i, long value) {
685         if (array != null) array[i] = value;
686     }
687 
addLong(long[] array, int i, long value)688     private static void addLong(long[] array, int i, long value) {
689         if (array != null) array[i] += value;
690     }
691 
estimateResizeBuckets(long newBucketDuration)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
readFullLongArray(DataInputStream in)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          */
readVarLong(DataInputStream in)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          */
writeVarLong(DataOutputStream out, long value)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 
readVarLongArray(DataInputStream in)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 
writeVarLongArray(DataOutputStream out, long[] values, int size)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 {
readLongArray(Parcel in)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 
writeLongArray(Parcel out, long[] values, int size)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