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