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