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