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 android.os.Parcel;
20 import android.os.Parcelable;
21 import android.os.SystemClock;
22 import android.util.SparseBooleanArray;
23 
24 import com.android.internal.annotations.VisibleForTesting;
25 import com.android.internal.util.ArrayUtils;
26 
27 import libcore.util.EmptyArray;
28 
29 import java.io.CharArrayWriter;
30 import java.io.PrintWriter;
31 import java.util.Arrays;
32 import java.util.HashSet;
33 import java.util.Objects;
34 
35 /**
36  * Collection of active network statistics. Can contain summary details across
37  * all interfaces, or details with per-UID granularity. Internally stores data
38  * as a large table, closely matching {@code /proc/} data format. This structure
39  * optimizes for rapid in-memory comparison, but consider using
40  * {@link NetworkStatsHistory} when persisting.
41  *
42  * @hide
43  */
44 public class NetworkStats implements Parcelable {
45     /** {@link #iface} value when interface details unavailable. */
46     public static final String IFACE_ALL = null;
47     /** {@link #uid} value when UID details unavailable. */
48     public static final int UID_ALL = -1;
49     /** {@link #tag} value matching any tag. */
50     public static final int TAG_ALL = -1;
51     /** {@link #set} value when all sets combined. */
52     public static final int SET_ALL = -1;
53     /** {@link #set} value where background data is accounted. */
54     public static final int SET_DEFAULT = 0;
55     /** {@link #set} value where foreground data is accounted. */
56     public static final int SET_FOREGROUND = 1;
57     /** {@link #tag} value for total data across all tags. */
58     public static final int TAG_NONE = 0;
59 
60     // TODO: move fields to "mVariable" notation
61 
62     /**
63      * {@link SystemClock#elapsedRealtime()} timestamp when this data was
64      * generated.
65      */
66     private long elapsedRealtime;
67     private int size;
68     private int capacity;
69     private String[] iface;
70     private int[] uid;
71     private int[] set;
72     private int[] tag;
73     private long[] rxBytes;
74     private long[] rxPackets;
75     private long[] txBytes;
76     private long[] txPackets;
77     private long[] operations;
78 
79     public static class Entry {
80         public String iface;
81         public int uid;
82         public int set;
83         public int tag;
84         public long rxBytes;
85         public long rxPackets;
86         public long txBytes;
87         public long txPackets;
88         public long operations;
89 
Entry()90         public Entry() {
91             this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
92         }
93 
Entry(long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)94         public Entry(long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) {
95             this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets,
96                     operations);
97         }
98 
Entry(String iface, int uid, int set, int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)99         public Entry(String iface, int uid, int set, int tag, long rxBytes, long rxPackets,
100                 long txBytes, long txPackets, long operations) {
101             this.iface = iface;
102             this.uid = uid;
103             this.set = set;
104             this.tag = tag;
105             this.rxBytes = rxBytes;
106             this.rxPackets = rxPackets;
107             this.txBytes = txBytes;
108             this.txPackets = txPackets;
109             this.operations = operations;
110         }
111 
isNegative()112         public boolean isNegative() {
113             return rxBytes < 0 || rxPackets < 0 || txBytes < 0 || txPackets < 0 || operations < 0;
114         }
115 
isEmpty()116         public boolean isEmpty() {
117             return rxBytes == 0 && rxPackets == 0 && txBytes == 0 && txPackets == 0
118                     && operations == 0;
119         }
120 
add(Entry another)121         public void add(Entry another) {
122             this.rxBytes += another.rxBytes;
123             this.rxPackets += another.rxPackets;
124             this.txBytes += another.txBytes;
125             this.txPackets += another.txPackets;
126             this.operations += another.operations;
127         }
128 
129         @Override
toString()130         public String toString() {
131             final StringBuilder builder = new StringBuilder();
132             builder.append("iface=").append(iface);
133             builder.append(" uid=").append(uid);
134             builder.append(" set=").append(setToString(set));
135             builder.append(" tag=").append(tagToString(tag));
136             builder.append(" rxBytes=").append(rxBytes);
137             builder.append(" rxPackets=").append(rxPackets);
138             builder.append(" txBytes=").append(txBytes);
139             builder.append(" txPackets=").append(txPackets);
140             builder.append(" operations=").append(operations);
141             return builder.toString();
142         }
143 
144         @Override
equals(Object o)145         public boolean equals(Object o) {
146             if (o instanceof Entry) {
147                 final Entry e = (Entry) o;
148                 return uid == e.uid && set == e.set && tag == e.tag && rxBytes == e.rxBytes
149                         && rxPackets == e.rxPackets && txBytes == e.txBytes
150                         && txPackets == e.txPackets && operations == e.operations
151                         && iface.equals(e.iface);
152             }
153             return false;
154         }
155     }
156 
NetworkStats(long elapsedRealtime, int initialSize)157     public NetworkStats(long elapsedRealtime, int initialSize) {
158         this.elapsedRealtime = elapsedRealtime;
159         this.size = 0;
160         if (initialSize >= 0) {
161             this.capacity = initialSize;
162             this.iface = new String[initialSize];
163             this.uid = new int[initialSize];
164             this.set = new int[initialSize];
165             this.tag = new int[initialSize];
166             this.rxBytes = new long[initialSize];
167             this.rxPackets = new long[initialSize];
168             this.txBytes = new long[initialSize];
169             this.txPackets = new long[initialSize];
170             this.operations = new long[initialSize];
171         } else {
172             // Special case for use by NetworkStatsFactory to start out *really* empty.
173             this.capacity = 0;
174             this.iface = EmptyArray.STRING;
175             this.uid = EmptyArray.INT;
176             this.set = EmptyArray.INT;
177             this.tag = EmptyArray.INT;
178             this.rxBytes = EmptyArray.LONG;
179             this.rxPackets = EmptyArray.LONG;
180             this.txBytes = EmptyArray.LONG;
181             this.txPackets = EmptyArray.LONG;
182             this.operations = EmptyArray.LONG;
183         }
184     }
185 
NetworkStats(Parcel parcel)186     public NetworkStats(Parcel parcel) {
187         elapsedRealtime = parcel.readLong();
188         size = parcel.readInt();
189         capacity = parcel.readInt();
190         iface = parcel.createStringArray();
191         uid = parcel.createIntArray();
192         set = parcel.createIntArray();
193         tag = parcel.createIntArray();
194         rxBytes = parcel.createLongArray();
195         rxPackets = parcel.createLongArray();
196         txBytes = parcel.createLongArray();
197         txPackets = parcel.createLongArray();
198         operations = parcel.createLongArray();
199     }
200 
201     @Override
writeToParcel(Parcel dest, int flags)202     public void writeToParcel(Parcel dest, int flags) {
203         dest.writeLong(elapsedRealtime);
204         dest.writeInt(size);
205         dest.writeInt(capacity);
206         dest.writeStringArray(iface);
207         dest.writeIntArray(uid);
208         dest.writeIntArray(set);
209         dest.writeIntArray(tag);
210         dest.writeLongArray(rxBytes);
211         dest.writeLongArray(rxPackets);
212         dest.writeLongArray(txBytes);
213         dest.writeLongArray(txPackets);
214         dest.writeLongArray(operations);
215     }
216 
217     @Override
clone()218     public NetworkStats clone() {
219         final NetworkStats clone = new NetworkStats(elapsedRealtime, size);
220         NetworkStats.Entry entry = null;
221         for (int i = 0; i < size; i++) {
222             entry = getValues(i, entry);
223             clone.addValues(entry);
224         }
225         return clone;
226     }
227 
228     @VisibleForTesting
addIfaceValues( String iface, long rxBytes, long rxPackets, long txBytes, long txPackets)229     public NetworkStats addIfaceValues(
230             String iface, long rxBytes, long rxPackets, long txBytes, long txPackets) {
231         return addValues(
232                 iface, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets, 0L);
233     }
234 
235     @VisibleForTesting
addValues(String iface, int uid, int set, int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)236     public NetworkStats addValues(String iface, int uid, int set, int tag, long rxBytes,
237             long rxPackets, long txBytes, long txPackets, long operations) {
238         return addValues(new Entry(
239                 iface, uid, set, tag, rxBytes, rxPackets, txBytes, txPackets, operations));
240     }
241 
242     /**
243      * Add new stats entry, copying from given {@link Entry}. The {@link Entry}
244      * object can be recycled across multiple calls.
245      */
addValues(Entry entry)246     public NetworkStats addValues(Entry entry) {
247         if (size >= capacity) {
248             final int newLength = Math.max(size, 10) * 3 / 2;
249             iface = Arrays.copyOf(iface, newLength);
250             uid = Arrays.copyOf(uid, newLength);
251             set = Arrays.copyOf(set, newLength);
252             tag = Arrays.copyOf(tag, newLength);
253             rxBytes = Arrays.copyOf(rxBytes, newLength);
254             rxPackets = Arrays.copyOf(rxPackets, newLength);
255             txBytes = Arrays.copyOf(txBytes, newLength);
256             txPackets = Arrays.copyOf(txPackets, newLength);
257             operations = Arrays.copyOf(operations, newLength);
258             capacity = newLength;
259         }
260 
261         iface[size] = entry.iface;
262         uid[size] = entry.uid;
263         set[size] = entry.set;
264         tag[size] = entry.tag;
265         rxBytes[size] = entry.rxBytes;
266         rxPackets[size] = entry.rxPackets;
267         txBytes[size] = entry.txBytes;
268         txPackets[size] = entry.txPackets;
269         operations[size] = entry.operations;
270         size++;
271 
272         return this;
273     }
274 
275     /**
276      * Return specific stats entry.
277      */
getValues(int i, Entry recycle)278     public Entry getValues(int i, Entry recycle) {
279         final Entry entry = recycle != null ? recycle : new Entry();
280         entry.iface = iface[i];
281         entry.uid = uid[i];
282         entry.set = set[i];
283         entry.tag = tag[i];
284         entry.rxBytes = rxBytes[i];
285         entry.rxPackets = rxPackets[i];
286         entry.txBytes = txBytes[i];
287         entry.txPackets = txPackets[i];
288         entry.operations = operations[i];
289         return entry;
290     }
291 
getElapsedRealtime()292     public long getElapsedRealtime() {
293         return elapsedRealtime;
294     }
295 
setElapsedRealtime(long time)296     public void setElapsedRealtime(long time) {
297         elapsedRealtime = time;
298     }
299 
300     /**
301      * Return age of this {@link NetworkStats} object with respect to
302      * {@link SystemClock#elapsedRealtime()}.
303      */
getElapsedRealtimeAge()304     public long getElapsedRealtimeAge() {
305         return SystemClock.elapsedRealtime() - elapsedRealtime;
306     }
307 
size()308     public int size() {
309         return size;
310     }
311 
312     @VisibleForTesting
internalSize()313     public int internalSize() {
314         return capacity;
315     }
316 
317     @Deprecated
combineValues(String iface, int uid, int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)318     public NetworkStats combineValues(String iface, int uid, int tag, long rxBytes, long rxPackets,
319             long txBytes, long txPackets, long operations) {
320         return combineValues(
321                 iface, uid, SET_DEFAULT, tag, rxBytes, rxPackets, txBytes, txPackets, operations);
322     }
323 
combineValues(String iface, int uid, int set, int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)324     public NetworkStats combineValues(String iface, int uid, int set, int tag, long rxBytes,
325             long rxPackets, long txBytes, long txPackets, long operations) {
326         return combineValues(new Entry(
327                 iface, uid, set, tag, rxBytes, rxPackets, txBytes, txPackets, operations));
328     }
329 
330     /**
331      * Combine given values with an existing row, or create a new row if
332      * {@link #findIndex(String, int, int, int)} is unable to find match. Can
333      * also be used to subtract values from existing rows.
334      */
combineValues(Entry entry)335     public NetworkStats combineValues(Entry entry) {
336         final int i = findIndex(entry.iface, entry.uid, entry.set, entry.tag);
337         if (i == -1) {
338             // only create new entry when positive contribution
339             addValues(entry);
340         } else {
341             rxBytes[i] += entry.rxBytes;
342             rxPackets[i] += entry.rxPackets;
343             txBytes[i] += entry.txBytes;
344             txPackets[i] += entry.txPackets;
345             operations[i] += entry.operations;
346         }
347         return this;
348     }
349 
350     /**
351      * Combine all values from another {@link NetworkStats} into this object.
352      */
combineAllValues(NetworkStats another)353     public void combineAllValues(NetworkStats another) {
354         NetworkStats.Entry entry = null;
355         for (int i = 0; i < another.size; i++) {
356             entry = another.getValues(i, entry);
357             combineValues(entry);
358         }
359     }
360 
361     /**
362      * Find first stats index that matches the requested parameters.
363      */
findIndex(String iface, int uid, int set, int tag)364     public int findIndex(String iface, int uid, int set, int tag) {
365         for (int i = 0; i < size; i++) {
366             if (uid == this.uid[i] && set == this.set[i] && tag == this.tag[i]
367                     && Objects.equals(iface, this.iface[i])) {
368                 return i;
369             }
370         }
371         return -1;
372     }
373 
374     /**
375      * Find first stats index that matches the requested parameters, starting
376      * search around the hinted index as an optimization.
377      */
378     @VisibleForTesting
findIndexHinted(String iface, int uid, int set, int tag, int hintIndex)379     public int findIndexHinted(String iface, int uid, int set, int tag, int hintIndex) {
380         for (int offset = 0; offset < size; offset++) {
381             final int halfOffset = offset / 2;
382 
383             // search outwards from hint index, alternating forward and backward
384             final int i;
385             if (offset % 2 == 0) {
386                 i = (hintIndex + halfOffset) % size;
387             } else {
388                 i = (size + hintIndex - halfOffset - 1) % size;
389             }
390 
391             if (uid == this.uid[i] && set == this.set[i] && tag == this.tag[i]
392                     && Objects.equals(iface, this.iface[i])) {
393                 return i;
394             }
395         }
396         return -1;
397     }
398 
399     /**
400      * Splice in {@link #operations} from the given {@link NetworkStats} based
401      * on matching {@link #uid} and {@link #tag} rows. Ignores {@link #iface},
402      * since operation counts are at data layer.
403      */
spliceOperationsFrom(NetworkStats stats)404     public void spliceOperationsFrom(NetworkStats stats) {
405         for (int i = 0; i < size; i++) {
406             final int j = stats.findIndex(iface[i], uid[i], set[i], tag[i]);
407             if (j == -1) {
408                 operations[i] = 0;
409             } else {
410                 operations[i] = stats.operations[j];
411             }
412         }
413     }
414 
415     /**
416      * Return list of unique interfaces known by this data structure.
417      */
getUniqueIfaces()418     public String[] getUniqueIfaces() {
419         final HashSet<String> ifaces = new HashSet<String>();
420         for (String iface : this.iface) {
421             if (iface != IFACE_ALL) {
422                 ifaces.add(iface);
423             }
424         }
425         return ifaces.toArray(new String[ifaces.size()]);
426     }
427 
428     /**
429      * Return list of unique UIDs known by this data structure.
430      */
getUniqueUids()431     public int[] getUniqueUids() {
432         final SparseBooleanArray uids = new SparseBooleanArray();
433         for (int uid : this.uid) {
434             uids.put(uid, true);
435         }
436 
437         final int size = uids.size();
438         final int[] result = new int[size];
439         for (int i = 0; i < size; i++) {
440             result[i] = uids.keyAt(i);
441         }
442         return result;
443     }
444 
445     /**
446      * Return total bytes represented by this snapshot object, usually used when
447      * checking if a {@link #subtract(NetworkStats)} delta passes a threshold.
448      */
getTotalBytes()449     public long getTotalBytes() {
450         final Entry entry = getTotal(null);
451         return entry.rxBytes + entry.txBytes;
452     }
453 
454     /**
455      * Return total of all fields represented by this snapshot object.
456      */
getTotal(Entry recycle)457     public Entry getTotal(Entry recycle) {
458         return getTotal(recycle, null, UID_ALL, false);
459     }
460 
461     /**
462      * Return total of all fields represented by this snapshot object matching
463      * the requested {@link #uid}.
464      */
getTotal(Entry recycle, int limitUid)465     public Entry getTotal(Entry recycle, int limitUid) {
466         return getTotal(recycle, null, limitUid, false);
467     }
468 
469     /**
470      * Return total of all fields represented by this snapshot object matching
471      * the requested {@link #iface}.
472      */
getTotal(Entry recycle, HashSet<String> limitIface)473     public Entry getTotal(Entry recycle, HashSet<String> limitIface) {
474         return getTotal(recycle, limitIface, UID_ALL, false);
475     }
476 
getTotalIncludingTags(Entry recycle)477     public Entry getTotalIncludingTags(Entry recycle) {
478         return getTotal(recycle, null, UID_ALL, true);
479     }
480 
481     /**
482      * Return total of all fields represented by this snapshot object matching
483      * the requested {@link #iface} and {@link #uid}.
484      *
485      * @param limitIface Set of {@link #iface} to include in total; or {@code
486      *            null} to include all ifaces.
487      */
getTotal( Entry recycle, HashSet<String> limitIface, int limitUid, boolean includeTags)488     private Entry getTotal(
489             Entry recycle, HashSet<String> limitIface, int limitUid, boolean includeTags) {
490         final Entry entry = recycle != null ? recycle : new Entry();
491 
492         entry.iface = IFACE_ALL;
493         entry.uid = limitUid;
494         entry.set = SET_ALL;
495         entry.tag = TAG_NONE;
496         entry.rxBytes = 0;
497         entry.rxPackets = 0;
498         entry.txBytes = 0;
499         entry.txPackets = 0;
500         entry.operations = 0;
501 
502         for (int i = 0; i < size; i++) {
503             final boolean matchesUid = (limitUid == UID_ALL) || (limitUid == uid[i]);
504             final boolean matchesIface = (limitIface == null) || (limitIface.contains(iface[i]));
505 
506             if (matchesUid && matchesIface) {
507                 // skip specific tags, since already counted in TAG_NONE
508                 if (tag[i] != TAG_NONE && !includeTags) continue;
509 
510                 entry.rxBytes += rxBytes[i];
511                 entry.rxPackets += rxPackets[i];
512                 entry.txBytes += txBytes[i];
513                 entry.txPackets += txPackets[i];
514                 entry.operations += operations[i];
515             }
516         }
517         return entry;
518     }
519 
520     /**
521      * Fast path for battery stats.
522      */
getTotalPackets()523     public long getTotalPackets() {
524         long total = 0;
525         for (int i = size-1; i >= 0; i--) {
526             total += rxPackets[i] + txPackets[i];
527         }
528         return total;
529     }
530 
531     /**
532      * Subtract the given {@link NetworkStats}, effectively leaving the delta
533      * between two snapshots in time. Assumes that statistics rows collect over
534      * time, and that none of them have disappeared.
535      */
subtract(NetworkStats right)536     public NetworkStats subtract(NetworkStats right) {
537         return subtract(this, right, null, null);
538     }
539 
540     /**
541      * Subtract the two given {@link NetworkStats} objects, returning the delta
542      * between two snapshots in time. Assumes that statistics rows collect over
543      * time, and that none of them have disappeared.
544      * <p>
545      * If counters have rolled backwards, they are clamped to {@code 0} and
546      * reported to the given {@link NonMonotonicObserver}.
547      */
subtract(NetworkStats left, NetworkStats right, NonMonotonicObserver<C> observer, C cookie)548     public static <C> NetworkStats subtract(NetworkStats left, NetworkStats right,
549             NonMonotonicObserver<C> observer, C cookie) {
550         return subtract(left, right, observer, cookie, null);
551     }
552 
553     /**
554      * Subtract the two given {@link NetworkStats} objects, returning the delta
555      * between two snapshots in time. Assumes that statistics rows collect over
556      * time, and that none of them have disappeared.
557      * <p>
558      * If counters have rolled backwards, they are clamped to {@code 0} and
559      * reported to the given {@link NonMonotonicObserver}.
560      * <p>
561      * If <var>recycle</var> is supplied, this NetworkStats object will be
562      * reused (and returned) as the result if it is large enough to contain
563      * the data.
564      */
subtract(NetworkStats left, NetworkStats right, NonMonotonicObserver<C> observer, C cookie, NetworkStats recycle)565     public static <C> NetworkStats subtract(NetworkStats left, NetworkStats right,
566             NonMonotonicObserver<C> observer, C cookie, NetworkStats recycle) {
567         long deltaRealtime = left.elapsedRealtime - right.elapsedRealtime;
568         if (deltaRealtime < 0) {
569             if (observer != null) {
570                 observer.foundNonMonotonic(left, -1, right, -1, cookie);
571             }
572             deltaRealtime = 0;
573         }
574 
575         // result will have our rows, and elapsed time between snapshots
576         final Entry entry = new Entry();
577         final NetworkStats result;
578         if (recycle != null && recycle.capacity >= left.size) {
579             result = recycle;
580             result.size = 0;
581             result.elapsedRealtime = deltaRealtime;
582         } else {
583             result = new NetworkStats(deltaRealtime, left.size);
584         }
585         for (int i = 0; i < left.size; i++) {
586             entry.iface = left.iface[i];
587             entry.uid = left.uid[i];
588             entry.set = left.set[i];
589             entry.tag = left.tag[i];
590 
591             // find remote row that matches, and subtract
592             final int j = right.findIndexHinted(entry.iface, entry.uid, entry.set, entry.tag, i);
593             if (j == -1) {
594                 // newly appearing row, return entire value
595                 entry.rxBytes = left.rxBytes[i];
596                 entry.rxPackets = left.rxPackets[i];
597                 entry.txBytes = left.txBytes[i];
598                 entry.txPackets = left.txPackets[i];
599                 entry.operations = left.operations[i];
600             } else {
601                 // existing row, subtract remote value
602                 entry.rxBytes = left.rxBytes[i] - right.rxBytes[j];
603                 entry.rxPackets = left.rxPackets[i] - right.rxPackets[j];
604                 entry.txBytes = left.txBytes[i] - right.txBytes[j];
605                 entry.txPackets = left.txPackets[i] - right.txPackets[j];
606                 entry.operations = left.operations[i] - right.operations[j];
607 
608                 if (entry.rxBytes < 0 || entry.rxPackets < 0 || entry.txBytes < 0
609                         || entry.txPackets < 0 || entry.operations < 0) {
610                     if (observer != null) {
611                         observer.foundNonMonotonic(left, i, right, j, cookie);
612                     }
613                     entry.rxBytes = Math.max(entry.rxBytes, 0);
614                     entry.rxPackets = Math.max(entry.rxPackets, 0);
615                     entry.txBytes = Math.max(entry.txBytes, 0);
616                     entry.txPackets = Math.max(entry.txPackets, 0);
617                     entry.operations = Math.max(entry.operations, 0);
618                 }
619             }
620 
621             result.addValues(entry);
622         }
623 
624         return result;
625     }
626 
627     /**
628      * Return total statistics grouped by {@link #iface}; doesn't mutate the
629      * original structure.
630      */
groupedByIface()631     public NetworkStats groupedByIface() {
632         final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
633 
634         final Entry entry = new Entry();
635         entry.uid = UID_ALL;
636         entry.set = SET_ALL;
637         entry.tag = TAG_NONE;
638         entry.operations = 0L;
639 
640         for (int i = 0; i < size; i++) {
641             // skip specific tags, since already counted in TAG_NONE
642             if (tag[i] != TAG_NONE) continue;
643 
644             entry.iface = iface[i];
645             entry.rxBytes = rxBytes[i];
646             entry.rxPackets = rxPackets[i];
647             entry.txBytes = txBytes[i];
648             entry.txPackets = txPackets[i];
649             stats.combineValues(entry);
650         }
651 
652         return stats;
653     }
654 
655     /**
656      * Return total statistics grouped by {@link #uid}; doesn't mutate the
657      * original structure.
658      */
groupedByUid()659     public NetworkStats groupedByUid() {
660         final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
661 
662         final Entry entry = new Entry();
663         entry.iface = IFACE_ALL;
664         entry.set = SET_ALL;
665         entry.tag = TAG_NONE;
666 
667         for (int i = 0; i < size; i++) {
668             // skip specific tags, since already counted in TAG_NONE
669             if (tag[i] != TAG_NONE) continue;
670 
671             entry.uid = uid[i];
672             entry.rxBytes = rxBytes[i];
673             entry.rxPackets = rxPackets[i];
674             entry.txBytes = txBytes[i];
675             entry.txPackets = txPackets[i];
676             entry.operations = operations[i];
677             stats.combineValues(entry);
678         }
679 
680         return stats;
681     }
682 
683     /**
684      * Return all rows except those attributed to the requested UID; doesn't
685      * mutate the original structure.
686      */
withoutUids(int[] uids)687     public NetworkStats withoutUids(int[] uids) {
688         final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
689 
690         Entry entry = new Entry();
691         for (int i = 0; i < size; i++) {
692             entry = getValues(i, entry);
693             if (!ArrayUtils.contains(uids, entry.uid)) {
694                 stats.addValues(entry);
695             }
696         }
697 
698         return stats;
699     }
700 
dump(String prefix, PrintWriter pw)701     public void dump(String prefix, PrintWriter pw) {
702         pw.print(prefix);
703         pw.print("NetworkStats: elapsedRealtime="); pw.println(elapsedRealtime);
704         for (int i = 0; i < size; i++) {
705             pw.print(prefix);
706             pw.print("  ["); pw.print(i); pw.print("]");
707             pw.print(" iface="); pw.print(iface[i]);
708             pw.print(" uid="); pw.print(uid[i]);
709             pw.print(" set="); pw.print(setToString(set[i]));
710             pw.print(" tag="); pw.print(tagToString(tag[i]));
711             pw.print(" rxBytes="); pw.print(rxBytes[i]);
712             pw.print(" rxPackets="); pw.print(rxPackets[i]);
713             pw.print(" txBytes="); pw.print(txBytes[i]);
714             pw.print(" txPackets="); pw.print(txPackets[i]);
715             pw.print(" operations="); pw.println(operations[i]);
716         }
717     }
718 
719     /**
720      * Return text description of {@link #set} value.
721      */
setToString(int set)722     public static String setToString(int set) {
723         switch (set) {
724             case SET_ALL:
725                 return "ALL";
726             case SET_DEFAULT:
727                 return "DEFAULT";
728             case SET_FOREGROUND:
729                 return "FOREGROUND";
730             default:
731                 return "UNKNOWN";
732         }
733     }
734 
735     /**
736      * Return text description of {@link #set} value.
737      */
setToCheckinString(int set)738     public static String setToCheckinString(int set) {
739         switch (set) {
740             case SET_ALL:
741                 return "all";
742             case SET_DEFAULT:
743                 return "def";
744             case SET_FOREGROUND:
745                 return "fg";
746             default:
747                 return "unk";
748         }
749     }
750 
751     /**
752      * Return text description of {@link #tag} value.
753      */
tagToString(int tag)754     public static String tagToString(int tag) {
755         return "0x" + Integer.toHexString(tag);
756     }
757 
758     @Override
toString()759     public String toString() {
760         final CharArrayWriter writer = new CharArrayWriter();
761         dump("", new PrintWriter(writer));
762         return writer.toString();
763     }
764 
765     @Override
describeContents()766     public int describeContents() {
767         return 0;
768     }
769 
770     public static final Creator<NetworkStats> CREATOR = new Creator<NetworkStats>() {
771         @Override
772         public NetworkStats createFromParcel(Parcel in) {
773             return new NetworkStats(in);
774         }
775 
776         @Override
777         public NetworkStats[] newArray(int size) {
778             return new NetworkStats[size];
779         }
780     };
781 
782     public interface NonMonotonicObserver<C> {
foundNonMonotonic( NetworkStats left, int leftIndex, NetworkStats right, int rightIndex, C cookie)783         public void foundNonMonotonic(
784                 NetworkStats left, int leftIndex, NetworkStats right, int rightIndex, C cookie);
785     }
786 }
787