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