• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.net;
18 
19 import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
20 import static android.net.NetworkStats.IFACE_ALL;
21 import static android.net.NetworkStats.SET_DEFAULT;
22 import static android.net.NetworkStats.TAG_NONE;
23 import static android.net.NetworkStats.UID_ALL;
24 import static android.net.NetworkStatsHistory.DataStreamUtils.readFullLongArray;
25 import static android.net.NetworkStatsHistory.DataStreamUtils.readVarLongArray;
26 import static android.net.NetworkStatsHistory.DataStreamUtils.writeVarLongArray;
27 import static android.net.NetworkStatsHistory.Entry.UNKNOWN;
28 import static android.net.NetworkStatsHistory.ParcelUtils.readLongArray;
29 import static android.net.NetworkStatsHistory.ParcelUtils.writeLongArray;
30 import static android.text.format.DateUtils.SECOND_IN_MILLIS;
31 
32 import static com.android.net.module.util.NetworkStatsUtils.multiplySafeByRational;
33 
34 import android.annotation.NonNull;
35 import android.annotation.Nullable;
36 import android.annotation.SystemApi;
37 import android.compat.annotation.UnsupportedAppUsage;
38 import android.os.Build;
39 import android.os.Parcel;
40 import android.os.Parcelable;
41 import android.service.NetworkStatsHistoryBucketProto;
42 import android.service.NetworkStatsHistoryProto;
43 import android.util.IndentingPrintWriter;
44 import android.util.proto.ProtoOutputStream;
45 
46 import com.android.net.module.util.CollectionUtils;
47 import com.android.net.module.util.NetworkStatsUtils;
48 
49 import libcore.util.EmptyArray;
50 
51 import java.io.CharArrayWriter;
52 import java.io.DataInput;
53 import java.io.DataOutput;
54 import java.io.IOException;
55 import java.io.PrintWriter;
56 import java.net.ProtocolException;
57 import java.util.ArrayList;
58 import java.util.Arrays;
59 import java.util.List;
60 import java.util.Random;
61 import java.util.TreeMap;
62 
63 /**
64  * Collection of historical network statistics, recorded into equally-sized
65  * "buckets" in time. Internally it stores data in {@code long} series for more
66  * efficient persistence.
67  * <p>
68  * Each bucket is defined by a {@link #bucketStart} timestamp, and lasts for
69  * {@link #bucketDuration}. Internally assumes that {@link #bucketStart} is
70  * sorted at all times.
71  *
72  * @hide
73  */
74 @SystemApi(client = MODULE_LIBRARIES)
75 public final class NetworkStatsHistory implements Parcelable {
76     private static final int VERSION_INIT = 1;
77     private static final int VERSION_ADD_PACKETS = 2;
78     private static final int VERSION_ADD_ACTIVE = 3;
79 
80     /** @hide */
81     public static final int FIELD_ACTIVE_TIME = 0x01;
82     /** @hide */
83     public static final int FIELD_RX_BYTES = 0x02;
84     /** @hide */
85     public static final int FIELD_RX_PACKETS = 0x04;
86     /** @hide */
87     public static final int FIELD_TX_BYTES = 0x08;
88     /** @hide */
89     public static final int FIELD_TX_PACKETS = 0x10;
90     /** @hide */
91     public static final int FIELD_OPERATIONS = 0x20;
92     /** @hide */
93     public static final int FIELD_ALL = 0xFFFFFFFF;
94 
95     private long bucketDuration;
96     private int bucketCount;
97     private long[] bucketStart;
98     private long[] activeTime;
99     private long[] rxBytes;
100     private long[] rxPackets;
101     private long[] txBytes;
102     private long[] txPackets;
103     private long[] operations;
104     private long totalBytes;
105 
106     /** @hide */
NetworkStatsHistory(long bucketDuration, long[] bucketStart, long[] activeTime, long[] rxBytes, long[] rxPackets, long[] txBytes, long[] txPackets, long[] operations, int bucketCount, long totalBytes)107     public NetworkStatsHistory(long bucketDuration, long[] bucketStart, long[] activeTime,
108             long[] rxBytes, long[] rxPackets, long[] txBytes, long[] txPackets,
109             long[] operations, int bucketCount, long totalBytes) {
110         this.bucketDuration = bucketDuration;
111         this.bucketStart = bucketStart;
112         this.activeTime = activeTime;
113         this.rxBytes = rxBytes;
114         this.rxPackets = rxPackets;
115         this.txBytes = txBytes;
116         this.txPackets = txPackets;
117         this.operations = operations;
118         this.bucketCount = bucketCount;
119         this.totalBytes = totalBytes;
120     }
121 
122     /**
123      * An instance to represent a single record in a {@link NetworkStatsHistory} object.
124      */
125     public static final class Entry {
126         /** @hide */
127         public static final long UNKNOWN = -1;
128 
129         /** @hide */
130         // TODO: Migrate all callers to get duration from the history object and remove this field.
131         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
132         public long bucketDuration;
133         /** @hide */
134         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
135         public long bucketStart;
136         /** @hide */
137         public long activeTime;
138         /** @hide */
139         @UnsupportedAppUsage
140         public long rxBytes;
141         /** @hide */
142         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
143         public long rxPackets;
144         /** @hide */
145         @UnsupportedAppUsage
146         public long txBytes;
147         /** @hide */
148         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
149         public long txPackets;
150         /** @hide */
151         public long operations;
152         /** @hide */
Entry()153         Entry() {}
154 
155         /**
156          * Construct a {@link Entry} instance to represent a single record in a
157          * {@link NetworkStatsHistory} object.
158          *
159          * @param bucketStart Start of period for this {@link Entry}, in milliseconds since the
160          *                    Unix epoch, see {@link java.lang.System#currentTimeMillis}.
161          * @param activeTime Active time for this {@link Entry}, in milliseconds.
162          * @param rxBytes Number of bytes received for this {@link Entry}. Statistics should
163          *                represent the contents of IP packets, including IP headers.
164          * @param rxPackets Number of packets received for this {@link Entry}. Statistics should
165          *                  represent the contents of IP packets, including IP headers.
166          * @param txBytes Number of bytes transmitted for this {@link Entry}. Statistics should
167          *                represent the contents of IP packets, including IP headers.
168          * @param txPackets Number of bytes transmitted for this {@link Entry}. Statistics should
169          *                  represent the contents of IP packets, including IP headers.
170          * @param operations count of network operations performed for this {@link Entry}. This can
171          *                   be used to derive bytes-per-operation.
172          */
Entry(long bucketStart, long activeTime, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)173         public Entry(long bucketStart, long activeTime, long rxBytes,
174                 long rxPackets, long txBytes, long txPackets, long operations) {
175             this.bucketStart = bucketStart;
176             this.activeTime = activeTime;
177             this.rxBytes = rxBytes;
178             this.rxPackets = rxPackets;
179             this.txBytes = txBytes;
180             this.txPackets = txPackets;
181             this.operations = operations;
182         }
183 
184         /**
185          * Get start timestamp of the bucket's time interval, in milliseconds since the Unix epoch.
186          */
getBucketStart()187         public long getBucketStart() {
188             return bucketStart;
189         }
190 
191         /**
192          * Get active time of the bucket's time interval, in milliseconds.
193          */
getActiveTime()194         public long getActiveTime() {
195             return activeTime;
196         }
197 
198         /** Get number of bytes received for this {@link Entry}. */
getRxBytes()199         public long getRxBytes() {
200             return rxBytes;
201         }
202 
203         /** Get number of packets received for this {@link Entry}. */
getRxPackets()204         public long getRxPackets() {
205             return rxPackets;
206         }
207 
208         /** Get number of bytes transmitted for this {@link Entry}. */
getTxBytes()209         public long getTxBytes() {
210             return txBytes;
211         }
212 
213         /** Get number of packets transmitted for this {@link Entry}. */
getTxPackets()214         public long getTxPackets() {
215             return txPackets;
216         }
217 
218         /** Get count of network operations performed for this {@link Entry}. */
getOperations()219         public long getOperations() {
220             return operations;
221         }
222 
223         @Override
equals(Object o)224         public boolean equals(Object o) {
225             if (this == o) return true;
226             if (o.getClass() != getClass()) return false;
227             Entry entry = (Entry) o;
228             return bucketStart == entry.bucketStart
229                     && activeTime == entry.activeTime && rxBytes == entry.rxBytes
230                     && rxPackets == entry.rxPackets && txBytes == entry.txBytes
231                     && txPackets == entry.txPackets && operations == entry.operations;
232         }
233 
234         @Override
hashCode()235         public int hashCode() {
236             return (int) (bucketStart * 2
237                     + activeTime * 3
238                     + rxBytes * 5
239                     + rxPackets * 7
240                     + txBytes * 11
241                     + txPackets * 13
242                     + operations * 17);
243         }
244 
245         @Override
toString()246         public String toString() {
247             return "Entry{"
248                     + "bucketStart=" + bucketStart
249                     + ", activeTime=" + activeTime
250                     + ", rxBytes=" + rxBytes
251                     + ", rxPackets=" + rxPackets
252                     + ", txBytes=" + txBytes
253                     + ", txPackets=" + txPackets
254                     + ", operations=" + operations
255                     + "}";
256         }
257 
258         /**
259          * Add the given {@link Entry} with this instance and return a new {@link Entry}
260          * instance as the result.
261          *
262          * @hide
263          */
264         @NonNull
plus(@onNull Entry another, long bucketDuration)265         public Entry plus(@NonNull Entry another, long bucketDuration) {
266             if (this.bucketStart != another.bucketStart) {
267                 throw new IllegalArgumentException("bucketStart " + this.bucketStart
268                         + " is not equal to " + another.bucketStart);
269             }
270             return new Entry(this.bucketStart,
271                     // Active time should not go over bucket duration.
272                     Math.min(this.activeTime + another.activeTime, bucketDuration),
273                     this.rxBytes + another.rxBytes,
274                     this.rxPackets + another.rxPackets,
275                     this.txBytes + another.txBytes,
276                     this.txPackets + another.txPackets,
277                     this.operations + another.operations);
278         }
279     }
280 
281     /** @hide */
282     @UnsupportedAppUsage
NetworkStatsHistory(long bucketDuration)283     public NetworkStatsHistory(long bucketDuration) {
284         this(bucketDuration, 10, FIELD_ALL);
285     }
286 
287     /** @hide */
NetworkStatsHistory(long bucketDuration, int initialSize)288     public NetworkStatsHistory(long bucketDuration, int initialSize) {
289         this(bucketDuration, initialSize, FIELD_ALL);
290     }
291 
292     /** @hide */
NetworkStatsHistory(long bucketDuration, int initialSize, int fields)293     public NetworkStatsHistory(long bucketDuration, int initialSize, int fields) {
294         this.bucketDuration = bucketDuration;
295         bucketStart = new long[initialSize];
296         if ((fields & FIELD_ACTIVE_TIME) != 0) activeTime = new long[initialSize];
297         if ((fields & FIELD_RX_BYTES) != 0) rxBytes = new long[initialSize];
298         if ((fields & FIELD_RX_PACKETS) != 0) rxPackets = new long[initialSize];
299         if ((fields & FIELD_TX_BYTES) != 0) txBytes = new long[initialSize];
300         if ((fields & FIELD_TX_PACKETS) != 0) txPackets = new long[initialSize];
301         if ((fields & FIELD_OPERATIONS) != 0) operations = new long[initialSize];
302         bucketCount = 0;
303         totalBytes = 0;
304     }
305 
306     /** @hide */
NetworkStatsHistory(NetworkStatsHistory existing, long bucketDuration)307     public NetworkStatsHistory(NetworkStatsHistory existing, long bucketDuration) {
308         this(bucketDuration, existing.estimateResizeBuckets(bucketDuration));
309         recordEntireHistory(existing);
310     }
311 
312     /** @hide */
313     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
NetworkStatsHistory(Parcel in)314     public NetworkStatsHistory(Parcel in) {
315         bucketDuration = in.readLong();
316         bucketStart = readLongArray(in);
317         activeTime = readLongArray(in);
318         rxBytes = readLongArray(in);
319         rxPackets = readLongArray(in);
320         txBytes = readLongArray(in);
321         txPackets = readLongArray(in);
322         operations = readLongArray(in);
323         bucketCount = bucketStart.length;
324         totalBytes = in.readLong();
325     }
326 
327     @Override
writeToParcel(@onNull Parcel out, int flags)328     public void writeToParcel(@NonNull Parcel out, int flags) {
329         out.writeLong(bucketDuration);
330         writeLongArray(out, bucketStart, bucketCount);
331         writeLongArray(out, activeTime, bucketCount);
332         writeLongArray(out, rxBytes, bucketCount);
333         writeLongArray(out, rxPackets, bucketCount);
334         writeLongArray(out, txBytes, bucketCount);
335         writeLongArray(out, txPackets, bucketCount);
336         writeLongArray(out, operations, bucketCount);
337         out.writeLong(totalBytes);
338     }
339 
340     /** @hide */
NetworkStatsHistory(DataInput in)341     public NetworkStatsHistory(DataInput in) throws IOException {
342         final int version = in.readInt();
343         switch (version) {
344             case VERSION_INIT: {
345                 bucketDuration = in.readLong();
346                 bucketStart = readFullLongArray(in);
347                 rxBytes = readFullLongArray(in);
348                 rxPackets = new long[bucketStart.length];
349                 txBytes = readFullLongArray(in);
350                 txPackets = new long[bucketStart.length];
351                 operations = new long[bucketStart.length];
352                 bucketCount = bucketStart.length;
353                 totalBytes = CollectionUtils.total(rxBytes) + CollectionUtils.total(txBytes);
354                 break;
355             }
356             case VERSION_ADD_PACKETS:
357             case VERSION_ADD_ACTIVE: {
358                 bucketDuration = in.readLong();
359                 bucketStart = readVarLongArray(in);
360                 activeTime = (version >= VERSION_ADD_ACTIVE) ? readVarLongArray(in)
361                         : new long[bucketStart.length];
362                 rxBytes = readVarLongArray(in);
363                 rxPackets = readVarLongArray(in);
364                 txBytes = readVarLongArray(in);
365                 txPackets = readVarLongArray(in);
366                 operations = readVarLongArray(in);
367                 bucketCount = bucketStart.length;
368                 totalBytes = CollectionUtils.total(rxBytes) + CollectionUtils.total(txBytes);
369                 break;
370             }
371             default: {
372                 throw new ProtocolException("unexpected version: " + version);
373             }
374         }
375 
376         if (bucketStart.length != bucketCount || rxBytes.length != bucketCount
377                 || rxPackets.length != bucketCount || txBytes.length != bucketCount
378                 || txPackets.length != bucketCount || operations.length != bucketCount) {
379             throw new ProtocolException("Mismatched history lengths");
380         }
381     }
382 
383     /** @hide */
writeToStream(DataOutput out)384     public void writeToStream(DataOutput out) throws IOException {
385         out.writeInt(VERSION_ADD_ACTIVE);
386         out.writeLong(bucketDuration);
387         writeVarLongArray(out, bucketStart, bucketCount);
388         writeVarLongArray(out, activeTime, bucketCount);
389         writeVarLongArray(out, rxBytes, bucketCount);
390         writeVarLongArray(out, rxPackets, bucketCount);
391         writeVarLongArray(out, txBytes, bucketCount);
392         writeVarLongArray(out, txPackets, bucketCount);
393         writeVarLongArray(out, operations, bucketCount);
394     }
395 
396     @Override
describeContents()397     public int describeContents() {
398         return 0;
399     }
400 
401     /** @hide */
402     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
size()403     public int size() {
404         return bucketCount;
405     }
406 
407     /** @hide */
getBucketDuration()408     public long getBucketDuration() {
409         return bucketDuration;
410     }
411 
412     /** @hide */
413     @UnsupportedAppUsage
getStart()414     public long getStart() {
415         if (bucketCount > 0) {
416             return bucketStart[0];
417         } else {
418             return Long.MAX_VALUE;
419         }
420     }
421 
422     /** @hide */
423     @UnsupportedAppUsage
getEnd()424     public long getEnd() {
425         if (bucketCount > 0) {
426             return bucketStart[bucketCount - 1] + bucketDuration;
427         } else {
428             return Long.MIN_VALUE;
429         }
430     }
431 
432     /**
433      * Return total bytes represented by this history.
434      * @hide
435      */
getTotalBytes()436     public long getTotalBytes() {
437         return totalBytes;
438     }
439 
440     /**
441      * Return index of bucket that contains or is immediately before the
442      * requested time.
443      * @hide
444      */
445     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getIndexBefore(long time)446     public int getIndexBefore(long time) {
447         int index = Arrays.binarySearch(bucketStart, 0, bucketCount, time);
448         if (index < 0) {
449             index = (~index) - 1;
450         } else {
451             index -= 1;
452         }
453         return NetworkStatsUtils.constrain(index, 0, bucketCount - 1);
454     }
455 
456     /**
457      * Return index of bucket that contains or is immediately after the
458      * requested time.
459      * @hide
460      */
getIndexAfter(long time)461     public int getIndexAfter(long time) {
462         int index = Arrays.binarySearch(bucketStart, 0, bucketCount, time);
463         if (index < 0) {
464             index = ~index;
465         } else {
466             index += 1;
467         }
468         return NetworkStatsUtils.constrain(index, 0, bucketCount - 1);
469     }
470 
471     /**
472      * Return specific stats entry.
473      * @hide
474      */
475     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getValues(int i, Entry recycle)476     public Entry getValues(int i, Entry recycle) {
477         final Entry entry = recycle != null ? recycle : new Entry();
478         entry.bucketStart = bucketStart[i];
479         entry.bucketDuration = bucketDuration;
480         entry.activeTime = getLong(activeTime, i, UNKNOWN);
481         entry.rxBytes = getLong(rxBytes, i, UNKNOWN);
482         entry.rxPackets = getLong(rxPackets, i, UNKNOWN);
483         entry.txBytes = getLong(txBytes, i, UNKNOWN);
484         entry.txPackets = getLong(txPackets, i, UNKNOWN);
485         entry.operations = getLong(operations, i, UNKNOWN);
486         return entry;
487     }
488 
489     /**
490      * Get List of {@link Entry} of the {@link NetworkStatsHistory} instance.
491      *
492      * @return
493      */
494     @NonNull
getEntries()495     public List<Entry> getEntries() {
496         // TODO: Return a wrapper that uses this list instead, to prevent the returned result
497         //  from being changed.
498         final ArrayList<Entry> ret = new ArrayList<>(size());
499         for (int i = 0; i < size(); i++) {
500             ret.add(getValues(i, null /* recycle */));
501         }
502         return ret;
503     }
504 
505     /** @hide */
setValues(int i, Entry entry)506     public void setValues(int i, Entry entry) {
507         // Unwind old values
508         if (rxBytes != null) totalBytes -= rxBytes[i];
509         if (txBytes != null) totalBytes -= txBytes[i];
510 
511         bucketStart[i] = entry.bucketStart;
512         setLong(activeTime, i, entry.activeTime);
513         setLong(rxBytes, i, entry.rxBytes);
514         setLong(rxPackets, i, entry.rxPackets);
515         setLong(txBytes, i, entry.txBytes);
516         setLong(txPackets, i, entry.txPackets);
517         setLong(operations, i, entry.operations);
518 
519         // Apply new values
520         if (rxBytes != null) totalBytes += rxBytes[i];
521         if (txBytes != null) totalBytes += txBytes[i];
522     }
523 
524     /**
525      * Record that data traffic occurred in the given time range. Will
526      * distribute across internal buckets, creating new buckets as needed.
527      * @hide
528      */
529     @Deprecated
recordData(long start, long end, long rxBytes, long txBytes)530     public void recordData(long start, long end, long rxBytes, long txBytes) {
531         recordData(start, end, new NetworkStats.Entry(
532                 IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, 0L, txBytes, 0L, 0L));
533     }
534 
535     /**
536      * Record that data traffic occurred in the given time range. Will
537      * distribute across internal buckets, creating new buckets as needed.
538      * @hide
539      */
recordData(long start, long end, NetworkStats.Entry entry)540     public void recordData(long start, long end, NetworkStats.Entry entry) {
541         long rxBytes = entry.rxBytes;
542         long rxPackets = entry.rxPackets;
543         long txBytes = entry.txBytes;
544         long txPackets = entry.txPackets;
545         long operations = entry.operations;
546 
547         if (entry.isNegative()) {
548             throw new IllegalArgumentException("tried recording negative data");
549         }
550         if (entry.isEmpty()) {
551             return;
552         }
553 
554         // create any buckets needed by this range
555         ensureBuckets(start, end);
556         // Return fast if there is still no entry. This would typically happen when the start,
557         // end or duration are not valid values, e.g. start > end, negative duration value, etc.
558         if (bucketCount == 0) return;
559 
560         // distribute data usage into buckets
561         long duration = end - start;
562         final int startIndex = getIndexAfter(end);
563         for (int i = startIndex; i >= 0; i--) {
564             final long curStart = bucketStart[i];
565             final long curEnd = curStart + bucketDuration;
566 
567             // bucket is older than record; we're finished
568             if (curEnd < start) break;
569             // bucket is newer than record; keep looking
570             if (curStart > end) continue;
571 
572             final long overlap = Math.min(curEnd, end) - Math.max(curStart, start);
573             if (overlap <= 0) continue;
574 
575             // integer math each time is faster than floating point
576             final long fracRxBytes = multiplySafeByRational(rxBytes, overlap, duration);
577             final long fracRxPackets = multiplySafeByRational(rxPackets, overlap, duration);
578             final long fracTxBytes = multiplySafeByRational(txBytes, overlap, duration);
579             final long fracTxPackets = multiplySafeByRational(txPackets, overlap, duration);
580             final long fracOperations = multiplySafeByRational(operations, overlap, duration);
581 
582 
583             addLong(activeTime, i, overlap);
584             addLong(this.rxBytes, i, fracRxBytes); rxBytes -= fracRxBytes;
585             addLong(this.rxPackets, i, fracRxPackets); rxPackets -= fracRxPackets;
586             addLong(this.txBytes, i, fracTxBytes); txBytes -= fracTxBytes;
587             addLong(this.txPackets, i, fracTxPackets); txPackets -= fracTxPackets;
588             addLong(this.operations, i, fracOperations); operations -= fracOperations;
589 
590             duration -= overlap;
591         }
592 
593         totalBytes += entry.rxBytes + entry.txBytes;
594     }
595 
596     /**
597      * Record an entire {@link NetworkStatsHistory} into this history. Usually
598      * for combining together stats for external reporting.
599      * @hide
600      */
601     @UnsupportedAppUsage
recordEntireHistory(NetworkStatsHistory input)602     public void recordEntireHistory(NetworkStatsHistory input) {
603         recordHistory(input, Long.MIN_VALUE, Long.MAX_VALUE);
604     }
605 
606     /**
607      * Record given {@link NetworkStatsHistory} into this history, copying only
608      * buckets that atomically occur in the inclusive time range. Doesn't
609      * interpolate across partial buckets.
610      * @hide
611      */
recordHistory(NetworkStatsHistory input, long start, long end)612     public void recordHistory(NetworkStatsHistory input, long start, long end) {
613         final NetworkStats.Entry entry = new NetworkStats.Entry(
614                 IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
615         for (int i = 0; i < input.bucketCount; i++) {
616             final long bucketStart = input.bucketStart[i];
617             final long bucketEnd = bucketStart + input.bucketDuration;
618 
619             // skip when bucket is outside requested range
620             if (bucketStart < start || bucketEnd > end) continue;
621 
622             entry.rxBytes = getLong(input.rxBytes, i, 0L);
623             entry.rxPackets = getLong(input.rxPackets, i, 0L);
624             entry.txBytes = getLong(input.txBytes, i, 0L);
625             entry.txPackets = getLong(input.txPackets, i, 0L);
626             entry.operations = getLong(input.operations, i, 0L);
627 
628             recordData(bucketStart, bucketEnd, entry);
629         }
630     }
631 
632     /**
633      * Ensure that buckets exist for given time range, creating as needed.
634      */
ensureBuckets(long start, long end)635     private void ensureBuckets(long start, long end) {
636         // normalize incoming range to bucket boundaries
637         start -= start % bucketDuration;
638         end += (bucketDuration - (end % bucketDuration)) % bucketDuration;
639 
640         for (long now = start; now < end; now += bucketDuration) {
641             // try finding existing bucket
642             final int index = Arrays.binarySearch(bucketStart, 0, bucketCount, now);
643             if (index < 0) {
644                 // bucket missing, create and insert
645                 insertBucket(~index, now);
646             }
647         }
648     }
649 
650     /**
651      * Insert new bucket at requested index and starting time.
652      */
insertBucket(int index, long start)653     private void insertBucket(int index, long start) {
654         // create more buckets when needed
655         if (bucketCount >= bucketStart.length) {
656             final int newLength = Math.max(bucketStart.length, 10) * 3 / 2;
657             bucketStart = Arrays.copyOf(bucketStart, newLength);
658             if (activeTime != null) activeTime = Arrays.copyOf(activeTime, newLength);
659             if (rxBytes != null) rxBytes = Arrays.copyOf(rxBytes, newLength);
660             if (rxPackets != null) rxPackets = Arrays.copyOf(rxPackets, newLength);
661             if (txBytes != null) txBytes = Arrays.copyOf(txBytes, newLength);
662             if (txPackets != null) txPackets = Arrays.copyOf(txPackets, newLength);
663             if (operations != null) operations = Arrays.copyOf(operations, newLength);
664         }
665 
666         // create gap when inserting bucket in middle
667         if (index < bucketCount) {
668             final int dstPos = index + 1;
669             final int length = bucketCount - index;
670 
671             System.arraycopy(bucketStart, index, bucketStart, dstPos, length);
672             if (activeTime != null) System.arraycopy(activeTime, index, activeTime, dstPos, length);
673             if (rxBytes != null) System.arraycopy(rxBytes, index, rxBytes, dstPos, length);
674             if (rxPackets != null) System.arraycopy(rxPackets, index, rxPackets, dstPos, length);
675             if (txBytes != null) System.arraycopy(txBytes, index, txBytes, dstPos, length);
676             if (txPackets != null) System.arraycopy(txPackets, index, txPackets, dstPos, length);
677             if (operations != null) System.arraycopy(operations, index, operations, dstPos, length);
678         }
679 
680         bucketStart[index] = start;
681         setLong(activeTime, index, 0L);
682         setLong(rxBytes, index, 0L);
683         setLong(rxPackets, index, 0L);
684         setLong(txBytes, index, 0L);
685         setLong(txPackets, index, 0L);
686         setLong(operations, index, 0L);
687         bucketCount++;
688     }
689 
690     /**
691      * Clear all data stored in this object.
692      * @hide
693      */
clear()694     public void clear() {
695         bucketStart = EmptyArray.LONG;
696         if (activeTime != null) activeTime = EmptyArray.LONG;
697         if (rxBytes != null) rxBytes = EmptyArray.LONG;
698         if (rxPackets != null) rxPackets = EmptyArray.LONG;
699         if (txBytes != null) txBytes = EmptyArray.LONG;
700         if (txPackets != null) txPackets = EmptyArray.LONG;
701         if (operations != null) operations = EmptyArray.LONG;
702         bucketCount = 0;
703         totalBytes = 0;
704     }
705 
706     /**
707      * Remove buckets that start older than requested cutoff.
708      *
709      * This method will remove any bucket that contains any data older than the requested
710      * cutoff, even if that same bucket includes some data from after the cutoff.
711      *
712      * @hide
713      */
removeBucketsStartingBefore(final long cutoff)714     public void removeBucketsStartingBefore(final long cutoff) {
715         // TODO: Consider use getIndexBefore.
716         int i;
717         for (i = 0; i < bucketCount; i++) {
718             final long curStart = bucketStart[i];
719 
720             // This bucket starts after or at the cutoff, so it should be kept.
721             if (curStart >= cutoff) break;
722         }
723 
724         if (i > 0) {
725             final int length = bucketStart.length;
726             bucketStart = Arrays.copyOfRange(bucketStart, i, length);
727             if (activeTime != null) activeTime = Arrays.copyOfRange(activeTime, i, length);
728             if (rxBytes != null) rxBytes = Arrays.copyOfRange(rxBytes, i, length);
729             if (rxPackets != null) rxPackets = Arrays.copyOfRange(rxPackets, i, length);
730             if (txBytes != null) txBytes = Arrays.copyOfRange(txBytes, i, length);
731             if (txPackets != null) txPackets = Arrays.copyOfRange(txPackets, i, length);
732             if (operations != null) operations = Arrays.copyOfRange(operations, i, length);
733             bucketCount -= i;
734 
735             totalBytes = 0;
736             if (rxBytes != null) totalBytes += CollectionUtils.total(rxBytes);
737             if (txBytes != null) totalBytes += CollectionUtils.total(txBytes);
738         }
739     }
740 
741     /**
742      * Return interpolated data usage across the requested range. Interpolates
743      * across buckets, so values may be rounded slightly.
744      *
745      * <p>If the active bucket is not completed yet, it returns the proportional value of it
746      * based on its duration and the {@code end} param.
747      *
748      * @param start - start of the range, timestamp in milliseconds since the epoch.
749      * @param end - end of the range, timestamp in milliseconds since the epoch.
750      * @param recycle - entry instance for performance, could be null.
751      * @hide
752      */
753     @UnsupportedAppUsage
getValues(long start, long end, Entry recycle)754     public Entry getValues(long start, long end, Entry recycle) {
755         return getValues(start, end, Long.MAX_VALUE, recycle);
756     }
757 
758     /**
759      * Return interpolated data usage across the requested range. Interpolates
760      * across buckets, so values may be rounded slightly.
761      *
762      * @param start - start of the range, timestamp in milliseconds since the epoch.
763      * @param end - end of the range, timestamp in milliseconds since the epoch.
764      * @param now - current timestamp in milliseconds since the epoch (wall clock).
765      * @param recycle - entry instance for performance, could be null.
766      * @hide
767      */
768     @UnsupportedAppUsage
getValues(long start, long end, long now, Entry recycle)769     public Entry getValues(long start, long end, long now, Entry recycle) {
770         final Entry entry = recycle != null ? recycle : new Entry();
771         entry.bucketDuration = end - start;
772         entry.bucketStart = start;
773         entry.activeTime = activeTime != null ? 0 : UNKNOWN;
774         entry.rxBytes = rxBytes != null ? 0 : UNKNOWN;
775         entry.rxPackets = rxPackets != null ? 0 : UNKNOWN;
776         entry.txBytes = txBytes != null ? 0 : UNKNOWN;
777         entry.txPackets = txPackets != null ? 0 : UNKNOWN;
778         entry.operations = operations != null ? 0 : UNKNOWN;
779 
780         // Return fast if there is no entry.
781         if (bucketCount == 0) return entry;
782 
783         final int startIndex = getIndexAfter(end);
784         for (int i = startIndex; i >= 0; i--) {
785             final long curStart = bucketStart[i];
786             long curEnd = curStart + bucketDuration;
787 
788             // bucket is older than request; we're finished
789             if (curEnd <= start) break;
790             // bucket is newer than request; keep looking
791             if (curStart >= end) continue;
792 
793             // the active bucket is shorter then a normal completed bucket
794             if (curEnd > now) curEnd = now;
795             // usually this is simply bucketDuration
796             final long bucketSpan = curEnd - curStart;
797             // prevent division by zero
798             if (bucketSpan <= 0) continue;
799 
800             final long overlapEnd = curEnd < end ? curEnd : end;
801             final long overlapStart = curStart > start ? curStart : start;
802             final long overlap = overlapEnd - overlapStart;
803             if (overlap <= 0) continue;
804 
805             // integer math each time is faster than floating point
806             if (activeTime != null) {
807                 entry.activeTime += multiplySafeByRational(activeTime[i], overlap, bucketSpan);
808             }
809             if (rxBytes != null) {
810                 entry.rxBytes += multiplySafeByRational(rxBytes[i], overlap, bucketSpan);
811             }
812             if (rxPackets != null) {
813                 entry.rxPackets += multiplySafeByRational(rxPackets[i], overlap, bucketSpan);
814             }
815             if (txBytes != null) {
816                 entry.txBytes += multiplySafeByRational(txBytes[i], overlap, bucketSpan);
817             }
818             if (txPackets != null) {
819                 entry.txPackets += multiplySafeByRational(txPackets[i], overlap, bucketSpan);
820             }
821             if (operations != null) {
822                 entry.operations += multiplySafeByRational(operations[i], overlap, bucketSpan);
823             }
824         }
825         return entry;
826     }
827 
828     /**
829      * @deprecated only for temporary testing
830      * @hide
831      */
832     @Deprecated
generateRandom(long start, long end, long bytes)833     public void generateRandom(long start, long end, long bytes) {
834         final Random r = new Random();
835 
836         final float fractionRx = r.nextFloat();
837         final long rxBytes = (long) (bytes * fractionRx);
838         final long txBytes = (long) (bytes * (1 - fractionRx));
839 
840         final long rxPackets = rxBytes / 1024;
841         final long txPackets = txBytes / 1024;
842         final long operations = rxBytes / 2048;
843 
844         generateRandom(start, end, rxBytes, rxPackets, txBytes, txPackets, operations, r);
845     }
846 
847     /**
848      * @deprecated only for temporary testing
849      * @hide
850      */
851     @Deprecated
generateRandom(long start, long end, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations, Random r)852     public void generateRandom(long start, long end, long rxBytes, long rxPackets, long txBytes,
853             long txPackets, long operations, Random r) {
854         ensureBuckets(start, end);
855 
856         final NetworkStats.Entry entry = new NetworkStats.Entry(
857                 IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
858         while (rxBytes > 1024 || rxPackets > 128 || txBytes > 1024 || txPackets > 128
859                 || operations > 32) {
860             final long curStart = randomLong(r, start, end);
861             final long curEnd = curStart + randomLong(r, 0, (end - curStart) / 2);
862 
863             entry.rxBytes = randomLong(r, 0, rxBytes);
864             entry.rxPackets = randomLong(r, 0, rxPackets);
865             entry.txBytes = randomLong(r, 0, txBytes);
866             entry.txPackets = randomLong(r, 0, txPackets);
867             entry.operations = randomLong(r, 0, operations);
868 
869             rxBytes -= entry.rxBytes;
870             rxPackets -= entry.rxPackets;
871             txBytes -= entry.txBytes;
872             txPackets -= entry.txPackets;
873             operations -= entry.operations;
874 
875             recordData(curStart, curEnd, entry);
876         }
877     }
878 
879     /** @hide */
randomLong(Random r, long start, long end)880     public static long randomLong(Random r, long start, long end) {
881         return (long) (start + (r.nextFloat() * (end - start)));
882     }
883 
884     /**
885      * Quickly determine if this history intersects with given window.
886      * @hide
887      */
intersects(long start, long end)888     public boolean intersects(long start, long end) {
889         final long dataStart = getStart();
890         final long dataEnd = getEnd();
891         if (start >= dataStart && start <= dataEnd) return true;
892         if (end >= dataStart && end <= dataEnd) return true;
893         if (dataStart >= start && dataStart <= end) return true;
894         if (dataEnd >= start && dataEnd <= end) return true;
895         return false;
896     }
897 
898     /** @hide */
dump(IndentingPrintWriter pw, boolean fullHistory)899     public void dump(IndentingPrintWriter pw, boolean fullHistory) {
900         pw.print("NetworkStatsHistory: bucketDuration=");
901         pw.println(bucketDuration / SECOND_IN_MILLIS);
902         pw.increaseIndent();
903 
904         final int start = fullHistory ? 0 : Math.max(0, bucketCount - 32);
905         if (start > 0) {
906             pw.print("(omitting "); pw.print(start); pw.println(" buckets)");
907         }
908 
909         for (int i = start; i < bucketCount; i++) {
910             pw.print("st="); pw.print(bucketStart[i] / SECOND_IN_MILLIS);
911             if (rxBytes != null) { pw.print(" rb="); pw.print(rxBytes[i]); }
912             if (rxPackets != null) { pw.print(" rp="); pw.print(rxPackets[i]); }
913             if (txBytes != null) { pw.print(" tb="); pw.print(txBytes[i]); }
914             if (txPackets != null) { pw.print(" tp="); pw.print(txPackets[i]); }
915             if (operations != null) { pw.print(" op="); pw.print(operations[i]); }
916             pw.println();
917         }
918 
919         pw.decreaseIndent();
920     }
921 
922     /** @hide */
dumpCheckin(PrintWriter pw)923     public void dumpCheckin(PrintWriter pw) {
924         pw.print("d,");
925         pw.print(bucketDuration / SECOND_IN_MILLIS);
926         pw.println();
927 
928         for (int i = 0; i < bucketCount; i++) {
929             pw.print("b,");
930             pw.print(bucketStart[i] / SECOND_IN_MILLIS); pw.print(',');
931             if (rxBytes != null) { pw.print(rxBytes[i]); } else { pw.print("*"); } pw.print(',');
932             if (rxPackets != null) { pw.print(rxPackets[i]); } else { pw.print("*"); } pw.print(',');
933             if (txBytes != null) { pw.print(txBytes[i]); } else { pw.print("*"); } pw.print(',');
934             if (txPackets != null) { pw.print(txPackets[i]); } else { pw.print("*"); } pw.print(',');
935             if (operations != null) { pw.print(operations[i]); } else { pw.print("*"); }
936             pw.println();
937         }
938     }
939 
940     /** @hide */
dumpDebug(ProtoOutputStream proto, long tag)941     public void dumpDebug(ProtoOutputStream proto, long tag) {
942         final long start = proto.start(tag);
943 
944         proto.write(NetworkStatsHistoryProto.BUCKET_DURATION_MS, bucketDuration);
945 
946         for (int i = 0; i < bucketCount; i++) {
947             final long startBucket = proto.start(NetworkStatsHistoryProto.BUCKETS);
948 
949             proto.write(NetworkStatsHistoryBucketProto.BUCKET_START_MS,
950                     bucketStart[i]);
951             dumpDebug(proto, NetworkStatsHistoryBucketProto.RX_BYTES, rxBytes, i);
952             dumpDebug(proto, NetworkStatsHistoryBucketProto.RX_PACKETS, rxPackets, i);
953             dumpDebug(proto, NetworkStatsHistoryBucketProto.TX_BYTES, txBytes, i);
954             dumpDebug(proto, NetworkStatsHistoryBucketProto.TX_PACKETS, txPackets, i);
955             dumpDebug(proto, NetworkStatsHistoryBucketProto.OPERATIONS, operations, i);
956 
957             proto.end(startBucket);
958         }
959 
960         proto.end(start);
961     }
962 
dumpDebug(ProtoOutputStream proto, long tag, long[] array, int index)963     private static void dumpDebug(ProtoOutputStream proto, long tag, long[] array, int index) {
964         if (array != null) {
965             proto.write(tag, array[index]);
966         }
967     }
968 
969     @Override
toString()970     public String toString() {
971         final CharArrayWriter writer = new CharArrayWriter();
972         dump(new IndentingPrintWriter(writer, "  "), false);
973         return writer.toString();
974     }
975 
976     /**
977      * Same as "equals", but not actually called equals as this would affect public API behavior.
978      * @hide
979      */
980     @Nullable
isSameAs(NetworkStatsHistory other)981     public boolean isSameAs(NetworkStatsHistory other) {
982         return bucketCount == other.bucketCount
983                 && Arrays.equals(bucketStart, other.bucketStart)
984                 // Don't check activeTime since it can change on import due to the importer using
985                 // recordHistory. It's also not exposed by the APIs or present in dumpsys or
986                 // toString().
987                 && Arrays.equals(rxBytes, other.rxBytes)
988                 && Arrays.equals(rxPackets, other.rxPackets)
989                 && Arrays.equals(txBytes, other.txBytes)
990                 && Arrays.equals(txPackets, other.txPackets)
991                 && Arrays.equals(operations, other.operations)
992                 && totalBytes == other.totalBytes;
993     }
994 
995     @UnsupportedAppUsage
996     public static final @android.annotation.NonNull Creator<NetworkStatsHistory> CREATOR = new Creator<NetworkStatsHistory>() {
997         @Override
998         public NetworkStatsHistory createFromParcel(Parcel in) {
999             return new NetworkStatsHistory(in);
1000         }
1001 
1002         @Override
1003         public NetworkStatsHistory[] newArray(int size) {
1004             return new NetworkStatsHistory[size];
1005         }
1006     };
1007 
getLong(long[] array, int i, long value)1008     private static long getLong(long[] array, int i, long value) {
1009         return array != null ? array[i] : value;
1010     }
1011 
setLong(long[] array, int i, long value)1012     private static void setLong(long[] array, int i, long value) {
1013         if (array != null) array[i] = value;
1014     }
1015 
addLong(long[] array, int i, long value)1016     private static void addLong(long[] array, int i, long value) {
1017         if (array != null) array[i] += value;
1018     }
1019 
1020     /** @hide */
estimateResizeBuckets(long newBucketDuration)1021     public int estimateResizeBuckets(long newBucketDuration) {
1022         return (int) (size() * getBucketDuration() / newBucketDuration);
1023     }
1024 
1025     /**
1026      * Utility methods for interacting with {@link DataInputStream} and
1027      * {@link DataOutputStream}, mostly dealing with writing partial arrays.
1028      * @hide
1029      */
1030     public static class DataStreamUtils {
1031         @Deprecated
readFullLongArray(DataInput in)1032         public static long[] readFullLongArray(DataInput in) throws IOException {
1033             final int size = in.readInt();
1034             if (size < 0) throw new ProtocolException("negative array size");
1035             final long[] values = new long[size];
1036             for (int i = 0; i < values.length; i++) {
1037                 values[i] = in.readLong();
1038             }
1039             return values;
1040         }
1041 
1042         /**
1043          * Read variable-length {@link Long} using protobuf-style approach.
1044          */
readVarLong(DataInput in)1045         public static long readVarLong(DataInput in) throws IOException {
1046             int shift = 0;
1047             long result = 0;
1048             while (shift < 64) {
1049                 byte b = in.readByte();
1050                 result |= (long) (b & 0x7F) << shift;
1051                 if ((b & 0x80) == 0)
1052                     return result;
1053                 shift += 7;
1054             }
1055             throw new ProtocolException("malformed long");
1056         }
1057 
1058         /**
1059          * Write variable-length {@link Long} using protobuf-style approach.
1060          */
writeVarLong(DataOutput out, long value)1061         public static void writeVarLong(DataOutput out, long value) throws IOException {
1062             while (true) {
1063                 if ((value & ~0x7FL) == 0) {
1064                     out.writeByte((int) value);
1065                     return;
1066                 } else {
1067                     out.writeByte(((int) value & 0x7F) | 0x80);
1068                     value >>>= 7;
1069                 }
1070             }
1071         }
1072 
readVarLongArray(DataInput in)1073         public static long[] readVarLongArray(DataInput in) throws IOException {
1074             final int size = in.readInt();
1075             if (size == -1) return null;
1076             if (size < 0) throw new ProtocolException("negative array size");
1077             final long[] values = new long[size];
1078             for (int i = 0; i < values.length; i++) {
1079                 values[i] = readVarLong(in);
1080             }
1081             return values;
1082         }
1083 
writeVarLongArray(DataOutput out, long[] values, int size)1084         public static void writeVarLongArray(DataOutput out, long[] values, int size)
1085                 throws IOException {
1086             if (values == null) {
1087                 out.writeInt(-1);
1088                 return;
1089             }
1090             if (size > values.length) {
1091                 throw new IllegalArgumentException("size larger than length");
1092             }
1093             out.writeInt(size);
1094             for (int i = 0; i < size; i++) {
1095                 writeVarLong(out, values[i]);
1096             }
1097         }
1098     }
1099 
1100     /**
1101      * Utility methods for interacting with {@link Parcel} structures, mostly
1102      * dealing with writing partial arrays.
1103      * @hide
1104      */
1105     public static class ParcelUtils {
readLongArray(Parcel in)1106         public static long[] readLongArray(Parcel in) {
1107             final int size = in.readInt();
1108             if (size == -1) return null;
1109             final long[] values = new long[size];
1110             for (int i = 0; i < values.length; i++) {
1111                 values[i] = in.readLong();
1112             }
1113             return values;
1114         }
1115 
writeLongArray(Parcel out, long[] values, int size)1116         public static void writeLongArray(Parcel out, long[] values, int size) {
1117             if (values == null) {
1118                 out.writeInt(-1);
1119                 return;
1120             }
1121             if (size > values.length) {
1122                 throw new IllegalArgumentException("size larger than length");
1123             }
1124             out.writeInt(size);
1125             for (int i = 0; i < size; i++) {
1126                 out.writeLong(values[i]);
1127             }
1128         }
1129     }
1130 
1131     /**
1132      * Builder class for {@link NetworkStatsHistory}.
1133      */
1134     public static final class Builder {
1135         private final TreeMap<Long, Entry> mEntries;
1136         private final long mBucketDuration;
1137 
1138         /**
1139          * Creates a new Builder with given bucket duration and initial capacity to construct
1140          * {@link NetworkStatsHistory} objects.
1141          *
1142          * @param bucketDuration Duration of the buckets of the object, in milliseconds.
1143          * @param initialCapacity Estimated number of records.
1144          */
Builder(long bucketDuration, int initialCapacity)1145         public Builder(long bucketDuration, int initialCapacity) {
1146             mBucketDuration = bucketDuration;
1147             // Create a collection that is always sorted and can deduplicate items by the timestamp.
1148             mEntries = new TreeMap<>();
1149         }
1150 
1151         /**
1152          * Add an {@link Entry} into the {@link NetworkStatsHistory} instance. If the timestamp
1153          * already exists, the given {@link Entry} will be combined into existing entry.
1154          *
1155          * @param entry The target {@link Entry} object.
1156          * @return The builder object.
1157          */
1158         @NonNull
addEntry(@onNull Entry entry)1159         public Builder addEntry(@NonNull Entry entry) {
1160             final Entry existing = mEntries.get(entry.bucketStart);
1161             if (existing != null) {
1162                 mEntries.put(entry.bucketStart, existing.plus(entry, mBucketDuration));
1163             } else {
1164                 mEntries.put(entry.bucketStart, entry);
1165             }
1166             return this;
1167         }
1168 
sum(@onNull long[] array)1169         private static long sum(@NonNull long[] array) {
1170             long sum = 0L;
1171             for (long entry : array) {
1172                 sum += entry;
1173             }
1174             return sum;
1175         }
1176 
1177         /**
1178          * Builds the instance of the {@link NetworkStatsHistory}.
1179          *
1180          * @return the built instance of {@link NetworkStatsHistory}.
1181          */
1182         @NonNull
build()1183         public NetworkStatsHistory build() {
1184             int size = mEntries.size();
1185             final long[] bucketStart = new long[size];
1186             final long[] activeTime = new long[size];
1187             final long[] rxBytes = new long[size];
1188             final long[] rxPackets = new long[size];
1189             final long[] txBytes = new long[size];
1190             final long[] txPackets = new long[size];
1191             final long[] operations = new long[size];
1192 
1193             int i = 0;
1194             for (Entry entry : mEntries.values()) {
1195                 bucketStart[i] = entry.bucketStart;
1196                 activeTime[i] = entry.activeTime;
1197                 rxBytes[i] = entry.rxBytes;
1198                 rxPackets[i] = entry.rxPackets;
1199                 txBytes[i] = entry.txBytes;
1200                 txPackets[i] = entry.txPackets;
1201                 operations[i] = entry.operations;
1202                 i++;
1203             }
1204 
1205             return new NetworkStatsHistory(mBucketDuration, bucketStart, activeTime,
1206                     rxBytes, rxPackets, txBytes, txPackets, operations,
1207                     size, sum(rxBytes) + sum(txBytes));
1208         }
1209     }
1210 }
1211