• 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 android.os.Parcel;
20 import android.os.Parcelable;
21 import android.os.SystemClock;
22 import android.util.Slog;
23 import android.util.SparseBooleanArray;
24 
25 import com.android.internal.annotations.VisibleForTesting;
26 import com.android.internal.util.ArrayUtils;
27 
28 import libcore.util.EmptyArray;
29 
30 import java.io.CharArrayWriter;
31 import java.io.PrintWriter;
32 import java.util.Arrays;
33 import java.util.HashSet;
34 import java.util.Objects;
35 
36 /**
37  * Collection of active network statistics. Can contain summary details across
38  * all interfaces, or details with per-UID granularity. Internally stores data
39  * as a large table, closely matching {@code /proc/} data format. This structure
40  * optimizes for rapid in-memory comparison, but consider using
41  * {@link NetworkStatsHistory} when persisting.
42  *
43  * @hide
44  */
45 public class NetworkStats implements Parcelable {
46     private static final String TAG = "NetworkStats";
47     /** {@link #iface} value when interface details unavailable. */
48     public static final String IFACE_ALL = null;
49     /** {@link #uid} value when UID details unavailable. */
50     public static final int UID_ALL = -1;
51     /** {@link #tag} value matching any tag. */
52     // TODO: Rename TAG_ALL to TAG_ANY.
53     public static final int TAG_ALL = -1;
54     /** {@link #set} value for all sets combined, not including debug sets. */
55     public static final int SET_ALL = -1;
56     /** {@link #set} value where background data is accounted. */
57     public static final int SET_DEFAULT = 0;
58     /** {@link #set} value where foreground data is accounted. */
59     public static final int SET_FOREGROUND = 1;
60     /** All {@link #set} value greater than SET_DEBUG_START are debug {@link #set} values. */
61     public static final int SET_DEBUG_START = 1000;
62     /** Debug {@link #set} value when the VPN stats are moved in. */
63     public static final int SET_DBG_VPN_IN = 1001;
64     /** Debug {@link #set} value when the VPN stats are moved out of a vpn UID. */
65     public static final int SET_DBG_VPN_OUT = 1002;
66 
67     /** {@link #tag} value for total data across all tags. */
68     // TODO: Rename TAG_NONE to TAG_ALL.
69     public static final int TAG_NONE = 0;
70 
71     /** {@link #set} value for all roaming values. */
72     public static final int ROAMING_ALL = -1;
73     /** {@link #set} value where native, non-roaming data is accounted. */
74     public static final int ROAMING_NO = 0;
75     /** {@link #set} value where roaming data is accounted. */
76     public static final int ROAMING_YES = 1;
77 
78     // TODO: move fields to "mVariable" notation
79 
80     /**
81      * {@link SystemClock#elapsedRealtime()} timestamp when this data was
82      * generated.
83      */
84     private long elapsedRealtime;
85     private int size;
86     private int capacity;
87     private String[] iface;
88     private int[] uid;
89     private int[] set;
90     private int[] tag;
91     private int[] roaming;
92     private long[] rxBytes;
93     private long[] rxPackets;
94     private long[] txBytes;
95     private long[] txPackets;
96     private long[] operations;
97 
98     public static class Entry {
99         public String iface;
100         public int uid;
101         public int set;
102         public int tag;
103         /**
104          * Note that this is only populated w/ the default value when read from /proc or written
105          * to disk. We merge in the correct value when reporting this value to clients of
106          * getSummary().
107          */
108         public int roaming;
109         public long rxBytes;
110         public long rxPackets;
111         public long txBytes;
112         public long txPackets;
113         public long operations;
114 
Entry()115         public Entry() {
116             this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
117         }
118 
Entry(long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)119         public Entry(long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) {
120             this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets,
121                     operations);
122         }
123 
Entry(String iface, int uid, int set, int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)124         public Entry(String iface, int uid, int set, int tag, long rxBytes, long rxPackets,
125                 long txBytes, long txPackets, long operations) {
126             this(iface, uid, set, tag, ROAMING_NO, rxBytes, rxPackets, txBytes, txPackets,
127                     operations);
128         }
129 
Entry(String iface, int uid, int set, int tag, int roaming, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)130         public Entry(String iface, int uid, int set, int tag, int roaming, long rxBytes,
131                 long rxPackets, long txBytes, long txPackets, long operations) {
132             this.iface = iface;
133             this.uid = uid;
134             this.set = set;
135             this.tag = tag;
136             this.roaming = roaming;
137             this.rxBytes = rxBytes;
138             this.rxPackets = rxPackets;
139             this.txBytes = txBytes;
140             this.txPackets = txPackets;
141             this.operations = operations;
142         }
143 
isNegative()144         public boolean isNegative() {
145             return rxBytes < 0 || rxPackets < 0 || txBytes < 0 || txPackets < 0 || operations < 0;
146         }
147 
isEmpty()148         public boolean isEmpty() {
149             return rxBytes == 0 && rxPackets == 0 && txBytes == 0 && txPackets == 0
150                     && operations == 0;
151         }
152 
add(Entry another)153         public void add(Entry another) {
154             this.rxBytes += another.rxBytes;
155             this.rxPackets += another.rxPackets;
156             this.txBytes += another.txBytes;
157             this.txPackets += another.txPackets;
158             this.operations += another.operations;
159         }
160 
161         @Override
toString()162         public String toString() {
163             final StringBuilder builder = new StringBuilder();
164             builder.append("iface=").append(iface);
165             builder.append(" uid=").append(uid);
166             builder.append(" set=").append(setToString(set));
167             builder.append(" tag=").append(tagToString(tag));
168             builder.append(" roaming=").append(roamingToString(roaming));
169             builder.append(" rxBytes=").append(rxBytes);
170             builder.append(" rxPackets=").append(rxPackets);
171             builder.append(" txBytes=").append(txBytes);
172             builder.append(" txPackets=").append(txPackets);
173             builder.append(" operations=").append(operations);
174             return builder.toString();
175         }
176 
177         @Override
equals(Object o)178         public boolean equals(Object o) {
179             if (o instanceof Entry) {
180                 final Entry e = (Entry) o;
181                 return uid == e.uid && set == e.set && tag == e.tag && roaming == e.roaming
182                         && rxBytes == e.rxBytes && rxPackets == e.rxPackets && txBytes == e.txBytes
183                         && txPackets == e.txPackets && operations == e.operations
184                         && iface.equals(e.iface);
185             }
186             return false;
187         }
188     }
189 
NetworkStats(long elapsedRealtime, int initialSize)190     public NetworkStats(long elapsedRealtime, int initialSize) {
191         this.elapsedRealtime = elapsedRealtime;
192         this.size = 0;
193         if (initialSize >= 0) {
194             this.capacity = initialSize;
195             this.iface = new String[initialSize];
196             this.uid = new int[initialSize];
197             this.set = new int[initialSize];
198             this.tag = new int[initialSize];
199             this.roaming = new int[initialSize];
200             this.rxBytes = new long[initialSize];
201             this.rxPackets = new long[initialSize];
202             this.txBytes = new long[initialSize];
203             this.txPackets = new long[initialSize];
204             this.operations = new long[initialSize];
205         } else {
206             // Special case for use by NetworkStatsFactory to start out *really* empty.
207             this.capacity = 0;
208             this.iface = EmptyArray.STRING;
209             this.uid = EmptyArray.INT;
210             this.set = EmptyArray.INT;
211             this.tag = EmptyArray.INT;
212             this.roaming = EmptyArray.INT;
213             this.rxBytes = EmptyArray.LONG;
214             this.rxPackets = EmptyArray.LONG;
215             this.txBytes = EmptyArray.LONG;
216             this.txPackets = EmptyArray.LONG;
217             this.operations = EmptyArray.LONG;
218         }
219     }
220 
NetworkStats(Parcel parcel)221     public NetworkStats(Parcel parcel) {
222         elapsedRealtime = parcel.readLong();
223         size = parcel.readInt();
224         capacity = parcel.readInt();
225         iface = parcel.createStringArray();
226         uid = parcel.createIntArray();
227         set = parcel.createIntArray();
228         tag = parcel.createIntArray();
229         roaming = parcel.createIntArray();
230         rxBytes = parcel.createLongArray();
231         rxPackets = parcel.createLongArray();
232         txBytes = parcel.createLongArray();
233         txPackets = parcel.createLongArray();
234         operations = parcel.createLongArray();
235     }
236 
237     @Override
writeToParcel(Parcel dest, int flags)238     public void writeToParcel(Parcel dest, int flags) {
239         dest.writeLong(elapsedRealtime);
240         dest.writeInt(size);
241         dest.writeInt(capacity);
242         dest.writeStringArray(iface);
243         dest.writeIntArray(uid);
244         dest.writeIntArray(set);
245         dest.writeIntArray(tag);
246         dest.writeIntArray(roaming);
247         dest.writeLongArray(rxBytes);
248         dest.writeLongArray(rxPackets);
249         dest.writeLongArray(txBytes);
250         dest.writeLongArray(txPackets);
251         dest.writeLongArray(operations);
252     }
253 
254     @Override
clone()255     public NetworkStats clone() {
256         final NetworkStats clone = new NetworkStats(elapsedRealtime, size);
257         NetworkStats.Entry entry = null;
258         for (int i = 0; i < size; i++) {
259             entry = getValues(i, entry);
260             clone.addValues(entry);
261         }
262         return clone;
263     }
264 
265     @VisibleForTesting
addIfaceValues( String iface, long rxBytes, long rxPackets, long txBytes, long txPackets)266     public NetworkStats addIfaceValues(
267             String iface, long rxBytes, long rxPackets, long txBytes, long txPackets) {
268         return addValues(
269                 iface, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets, 0L);
270     }
271 
272     @VisibleForTesting
addValues(String iface, int uid, int set, int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)273     public NetworkStats addValues(String iface, int uid, int set, int tag, long rxBytes,
274             long rxPackets, long txBytes, long txPackets, long operations) {
275         return addValues(new Entry(
276                 iface, uid, set, tag, rxBytes, rxPackets, txBytes, txPackets, operations));
277     }
278 
279     @VisibleForTesting
addValues(String iface, int uid, int set, int tag, int roaming, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)280     public NetworkStats addValues(String iface, int uid, int set, int tag, int roaming,
281             long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) {
282         return addValues(new Entry(
283                 iface, uid, set, tag, roaming, rxBytes, rxPackets, txBytes, txPackets, operations));
284     }
285 
286     /**
287      * Add new stats entry, copying from given {@link Entry}. The {@link Entry}
288      * object can be recycled across multiple calls.
289      */
addValues(Entry entry)290     public NetworkStats addValues(Entry entry) {
291         if (size >= capacity) {
292             final int newLength = Math.max(size, 10) * 3 / 2;
293             iface = Arrays.copyOf(iface, newLength);
294             uid = Arrays.copyOf(uid, newLength);
295             set = Arrays.copyOf(set, newLength);
296             tag = Arrays.copyOf(tag, newLength);
297             roaming = Arrays.copyOf(roaming, newLength);
298             rxBytes = Arrays.copyOf(rxBytes, newLength);
299             rxPackets = Arrays.copyOf(rxPackets, newLength);
300             txBytes = Arrays.copyOf(txBytes, newLength);
301             txPackets = Arrays.copyOf(txPackets, newLength);
302             operations = Arrays.copyOf(operations, newLength);
303             capacity = newLength;
304         }
305 
306         iface[size] = entry.iface;
307         uid[size] = entry.uid;
308         set[size] = entry.set;
309         tag[size] = entry.tag;
310         roaming[size] = entry.roaming;
311         rxBytes[size] = entry.rxBytes;
312         rxPackets[size] = entry.rxPackets;
313         txBytes[size] = entry.txBytes;
314         txPackets[size] = entry.txPackets;
315         operations[size] = entry.operations;
316         size++;
317 
318         return this;
319     }
320 
321     /**
322      * Return specific stats entry.
323      */
getValues(int i, Entry recycle)324     public Entry getValues(int i, Entry recycle) {
325         final Entry entry = recycle != null ? recycle : new Entry();
326         entry.iface = iface[i];
327         entry.uid = uid[i];
328         entry.set = set[i];
329         entry.tag = tag[i];
330         entry.roaming = roaming[i];
331         entry.rxBytes = rxBytes[i];
332         entry.rxPackets = rxPackets[i];
333         entry.txBytes = txBytes[i];
334         entry.txPackets = txPackets[i];
335         entry.operations = operations[i];
336         return entry;
337     }
338 
getElapsedRealtime()339     public long getElapsedRealtime() {
340         return elapsedRealtime;
341     }
342 
setElapsedRealtime(long time)343     public void setElapsedRealtime(long time) {
344         elapsedRealtime = time;
345     }
346 
347     /**
348      * Return age of this {@link NetworkStats} object with respect to
349      * {@link SystemClock#elapsedRealtime()}.
350      */
getElapsedRealtimeAge()351     public long getElapsedRealtimeAge() {
352         return SystemClock.elapsedRealtime() - elapsedRealtime;
353     }
354 
size()355     public int size() {
356         return size;
357     }
358 
359     @VisibleForTesting
internalSize()360     public int internalSize() {
361         return capacity;
362     }
363 
364     @Deprecated
combineValues(String iface, int uid, int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)365     public NetworkStats combineValues(String iface, int uid, int tag, long rxBytes, long rxPackets,
366             long txBytes, long txPackets, long operations) {
367         return combineValues(
368                 iface, uid, SET_DEFAULT, tag, rxBytes, rxPackets, txBytes,
369                 txPackets, operations);
370     }
371 
combineValues(String iface, int uid, int set, int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)372     public NetworkStats combineValues(String iface, int uid, int set, int tag,
373             long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) {
374         return combineValues(new Entry(
375                 iface, uid, set, tag, rxBytes, rxPackets, txBytes, txPackets, operations));
376     }
377 
378     /**
379      * Combine given values with an existing row, or create a new row if
380      * {@link #findIndex(String, int, int, int, int)} is unable to find match. Can
381      * also be used to subtract values from existing rows.
382      */
combineValues(Entry entry)383     public NetworkStats combineValues(Entry entry) {
384         final int i = findIndex(entry.iface, entry.uid, entry.set, entry.tag, entry.roaming);
385         if (i == -1) {
386             // only create new entry when positive contribution
387             addValues(entry);
388         } else {
389             rxBytes[i] += entry.rxBytes;
390             rxPackets[i] += entry.rxPackets;
391             txBytes[i] += entry.txBytes;
392             txPackets[i] += entry.txPackets;
393             operations[i] += entry.operations;
394         }
395         return this;
396     }
397 
398     /**
399      * Combine all values from another {@link NetworkStats} into this object.
400      */
combineAllValues(NetworkStats another)401     public void combineAllValues(NetworkStats another) {
402         NetworkStats.Entry entry = null;
403         for (int i = 0; i < another.size; i++) {
404             entry = another.getValues(i, entry);
405             combineValues(entry);
406         }
407     }
408 
409     /**
410      * Find first stats index that matches the requested parameters.
411      */
findIndex(String iface, int uid, int set, int tag, int roaming)412     public int findIndex(String iface, int uid, int set, int tag, int roaming) {
413         for (int i = 0; i < size; i++) {
414             if (uid == this.uid[i] && set == this.set[i] && tag == this.tag[i]
415                     && roaming == this.roaming[i] && Objects.equals(iface, this.iface[i])) {
416                 return i;
417             }
418         }
419         return -1;
420     }
421 
422     /**
423      * Find first stats index that matches the requested parameters, starting
424      * search around the hinted index as an optimization.
425      */
426     @VisibleForTesting
findIndexHinted(String iface, int uid, int set, int tag, int roaming, int hintIndex)427     public int findIndexHinted(String iface, int uid, int set, int tag, int roaming,
428             int hintIndex) {
429         for (int offset = 0; offset < size; offset++) {
430             final int halfOffset = offset / 2;
431 
432             // search outwards from hint index, alternating forward and backward
433             final int i;
434             if (offset % 2 == 0) {
435                 i = (hintIndex + halfOffset) % size;
436             } else {
437                 i = (size + hintIndex - halfOffset - 1) % size;
438             }
439 
440             if (uid == this.uid[i] && set == this.set[i] && tag == this.tag[i]
441                     && roaming == this.roaming[i] && Objects.equals(iface, this.iface[i])) {
442                 return i;
443             }
444         }
445         return -1;
446     }
447 
448     /**
449      * Splice in {@link #operations} from the given {@link NetworkStats} based
450      * on matching {@link #uid} and {@link #tag} rows. Ignores {@link #iface},
451      * since operation counts are at data layer.
452      */
spliceOperationsFrom(NetworkStats stats)453     public void spliceOperationsFrom(NetworkStats stats) {
454         for (int i = 0; i < size; i++) {
455             final int j = stats.findIndex(iface[i], uid[i], set[i], tag[i], roaming[i]);
456             if (j == -1) {
457                 operations[i] = 0;
458             } else {
459                 operations[i] = stats.operations[j];
460             }
461         }
462     }
463 
464     /**
465      * Return list of unique interfaces known by this data structure.
466      */
getUniqueIfaces()467     public String[] getUniqueIfaces() {
468         final HashSet<String> ifaces = new HashSet<String>();
469         for (String iface : this.iface) {
470             if (iface != IFACE_ALL) {
471                 ifaces.add(iface);
472             }
473         }
474         return ifaces.toArray(new String[ifaces.size()]);
475     }
476 
477     /**
478      * Return list of unique UIDs known by this data structure.
479      */
getUniqueUids()480     public int[] getUniqueUids() {
481         final SparseBooleanArray uids = new SparseBooleanArray();
482         for (int uid : this.uid) {
483             uids.put(uid, true);
484         }
485 
486         final int size = uids.size();
487         final int[] result = new int[size];
488         for (int i = 0; i < size; i++) {
489             result[i] = uids.keyAt(i);
490         }
491         return result;
492     }
493 
494     /**
495      * Return total bytes represented by this snapshot object, usually used when
496      * checking if a {@link #subtract(NetworkStats)} delta passes a threshold.
497      */
getTotalBytes()498     public long getTotalBytes() {
499         final Entry entry = getTotal(null);
500         return entry.rxBytes + entry.txBytes;
501     }
502 
503     /**
504      * Return total of all fields represented by this snapshot object.
505      */
getTotal(Entry recycle)506     public Entry getTotal(Entry recycle) {
507         return getTotal(recycle, null, UID_ALL, false);
508     }
509 
510     /**
511      * Return total of all fields represented by this snapshot object matching
512      * the requested {@link #uid}.
513      */
getTotal(Entry recycle, int limitUid)514     public Entry getTotal(Entry recycle, int limitUid) {
515         return getTotal(recycle, null, limitUid, false);
516     }
517 
518     /**
519      * Return total of all fields represented by this snapshot object matching
520      * the requested {@link #iface}.
521      */
getTotal(Entry recycle, HashSet<String> limitIface)522     public Entry getTotal(Entry recycle, HashSet<String> limitIface) {
523         return getTotal(recycle, limitIface, UID_ALL, false);
524     }
525 
getTotalIncludingTags(Entry recycle)526     public Entry getTotalIncludingTags(Entry recycle) {
527         return getTotal(recycle, null, UID_ALL, true);
528     }
529 
530     /**
531      * Return total of all fields represented by this snapshot object matching
532      * the requested {@link #iface} and {@link #uid}.
533      *
534      * @param limitIface Set of {@link #iface} to include in total; or {@code
535      *            null} to include all ifaces.
536      */
getTotal( Entry recycle, HashSet<String> limitIface, int limitUid, boolean includeTags)537     private Entry getTotal(
538             Entry recycle, HashSet<String> limitIface, int limitUid, boolean includeTags) {
539         final Entry entry = recycle != null ? recycle : new Entry();
540 
541         entry.iface = IFACE_ALL;
542         entry.uid = limitUid;
543         entry.set = SET_ALL;
544         entry.tag = TAG_NONE;
545         entry.roaming = ROAMING_ALL;
546         entry.rxBytes = 0;
547         entry.rxPackets = 0;
548         entry.txBytes = 0;
549         entry.txPackets = 0;
550         entry.operations = 0;
551 
552         for (int i = 0; i < size; i++) {
553             final boolean matchesUid = (limitUid == UID_ALL) || (limitUid == uid[i]);
554             final boolean matchesIface = (limitIface == null) || (limitIface.contains(iface[i]));
555 
556             if (matchesUid && matchesIface) {
557                 // skip specific tags, since already counted in TAG_NONE
558                 if (tag[i] != TAG_NONE && !includeTags) continue;
559 
560                 entry.rxBytes += rxBytes[i];
561                 entry.rxPackets += rxPackets[i];
562                 entry.txBytes += txBytes[i];
563                 entry.txPackets += txPackets[i];
564                 entry.operations += operations[i];
565             }
566         }
567         return entry;
568     }
569 
570     /**
571      * Fast path for battery stats.
572      */
getTotalPackets()573     public long getTotalPackets() {
574         long total = 0;
575         for (int i = size-1; i >= 0; i--) {
576             total += rxPackets[i] + txPackets[i];
577         }
578         return total;
579     }
580 
581     /**
582      * Subtract the given {@link NetworkStats}, effectively leaving the delta
583      * between two snapshots in time. Assumes that statistics rows collect over
584      * time, and that none of them have disappeared.
585      */
subtract(NetworkStats right)586     public NetworkStats subtract(NetworkStats right) {
587         return subtract(this, right, null, null);
588     }
589 
590     /**
591      * Subtract the two given {@link NetworkStats} objects, returning the delta
592      * between two snapshots in time. Assumes that statistics rows collect over
593      * time, and that none of them have disappeared.
594      * <p>
595      * If counters have rolled backwards, they are clamped to {@code 0} and
596      * reported to the given {@link NonMonotonicObserver}.
597      */
subtract(NetworkStats left, NetworkStats right, NonMonotonicObserver<C> observer, C cookie)598     public static <C> NetworkStats subtract(NetworkStats left, NetworkStats right,
599             NonMonotonicObserver<C> observer, C cookie) {
600         return subtract(left, right, observer, cookie, null);
601     }
602 
603     /**
604      * Subtract the two given {@link NetworkStats} objects, returning the delta
605      * between two snapshots in time. Assumes that statistics rows collect over
606      * time, and that none of them have disappeared.
607      * <p>
608      * If counters have rolled backwards, they are clamped to {@code 0} and
609      * reported to the given {@link NonMonotonicObserver}.
610      * <p>
611      * If <var>recycle</var> is supplied, this NetworkStats object will be
612      * reused (and returned) as the result if it is large enough to contain
613      * the data.
614      */
subtract(NetworkStats left, NetworkStats right, NonMonotonicObserver<C> observer, C cookie, NetworkStats recycle)615     public static <C> NetworkStats subtract(NetworkStats left, NetworkStats right,
616             NonMonotonicObserver<C> observer, C cookie, NetworkStats recycle) {
617         long deltaRealtime = left.elapsedRealtime - right.elapsedRealtime;
618         if (deltaRealtime < 0) {
619             if (observer != null) {
620                 observer.foundNonMonotonic(left, -1, right, -1, cookie);
621             }
622             deltaRealtime = 0;
623         }
624 
625         // result will have our rows, and elapsed time between snapshots
626         final Entry entry = new Entry();
627         final NetworkStats result;
628         if (recycle != null && recycle.capacity >= left.size) {
629             result = recycle;
630             result.size = 0;
631             result.elapsedRealtime = deltaRealtime;
632         } else {
633             result = new NetworkStats(deltaRealtime, left.size);
634         }
635         for (int i = 0; i < left.size; i++) {
636             entry.iface = left.iface[i];
637             entry.uid = left.uid[i];
638             entry.set = left.set[i];
639             entry.tag = left.tag[i];
640             entry.roaming = left.roaming[i];
641 
642             // find remote row that matches, and subtract
643             final int j = right.findIndexHinted(entry.iface, entry.uid, entry.set, entry.tag,
644                     entry.roaming, i);
645             if (j == -1) {
646                 // newly appearing row, return entire value
647                 entry.rxBytes = left.rxBytes[i];
648                 entry.rxPackets = left.rxPackets[i];
649                 entry.txBytes = left.txBytes[i];
650                 entry.txPackets = left.txPackets[i];
651                 entry.operations = left.operations[i];
652             } else {
653                 // existing row, subtract remote value
654                 entry.rxBytes = left.rxBytes[i] - right.rxBytes[j];
655                 entry.rxPackets = left.rxPackets[i] - right.rxPackets[j];
656                 entry.txBytes = left.txBytes[i] - right.txBytes[j];
657                 entry.txPackets = left.txPackets[i] - right.txPackets[j];
658                 entry.operations = left.operations[i] - right.operations[j];
659 
660                 if (entry.rxBytes < 0 || entry.rxPackets < 0 || entry.txBytes < 0
661                         || entry.txPackets < 0 || entry.operations < 0) {
662                     if (observer != null) {
663                         observer.foundNonMonotonic(left, i, right, j, cookie);
664                     }
665                     entry.rxBytes = Math.max(entry.rxBytes, 0);
666                     entry.rxPackets = Math.max(entry.rxPackets, 0);
667                     entry.txBytes = Math.max(entry.txBytes, 0);
668                     entry.txPackets = Math.max(entry.txPackets, 0);
669                     entry.operations = Math.max(entry.operations, 0);
670                 }
671             }
672 
673             result.addValues(entry);
674         }
675 
676         return result;
677     }
678 
679     /**
680      * Return total statistics grouped by {@link #iface}; doesn't mutate the
681      * original structure.
682      */
groupedByIface()683     public NetworkStats groupedByIface() {
684         final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
685 
686         final Entry entry = new Entry();
687         entry.uid = UID_ALL;
688         entry.set = SET_ALL;
689         entry.tag = TAG_NONE;
690         entry.roaming = ROAMING_ALL;
691         entry.operations = 0L;
692 
693         for (int i = 0; i < size; i++) {
694             // skip specific tags, since already counted in TAG_NONE
695             if (tag[i] != TAG_NONE) continue;
696 
697             entry.iface = iface[i];
698             entry.rxBytes = rxBytes[i];
699             entry.rxPackets = rxPackets[i];
700             entry.txBytes = txBytes[i];
701             entry.txPackets = txPackets[i];
702             stats.combineValues(entry);
703         }
704 
705         return stats;
706     }
707 
708     /**
709      * Return total statistics grouped by {@link #uid}; doesn't mutate the
710      * original structure.
711      */
groupedByUid()712     public NetworkStats groupedByUid() {
713         final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
714 
715         final Entry entry = new Entry();
716         entry.iface = IFACE_ALL;
717         entry.set = SET_ALL;
718         entry.tag = TAG_NONE;
719         entry.roaming = ROAMING_ALL;
720 
721         for (int i = 0; i < size; i++) {
722             // skip specific tags, since already counted in TAG_NONE
723             if (tag[i] != TAG_NONE) continue;
724 
725             entry.uid = uid[i];
726             entry.rxBytes = rxBytes[i];
727             entry.rxPackets = rxPackets[i];
728             entry.txBytes = txBytes[i];
729             entry.txPackets = txPackets[i];
730             entry.operations = operations[i];
731             stats.combineValues(entry);
732         }
733 
734         return stats;
735     }
736 
737     /**
738      * Return all rows except those attributed to the requested UID; doesn't
739      * mutate the original structure.
740      */
withoutUids(int[] uids)741     public NetworkStats withoutUids(int[] uids) {
742         final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
743 
744         Entry entry = new Entry();
745         for (int i = 0; i < size; i++) {
746             entry = getValues(i, entry);
747             if (!ArrayUtils.contains(uids, entry.uid)) {
748                 stats.addValues(entry);
749             }
750         }
751 
752         return stats;
753     }
754 
dump(String prefix, PrintWriter pw)755     public void dump(String prefix, PrintWriter pw) {
756         pw.print(prefix);
757         pw.print("NetworkStats: elapsedRealtime="); pw.println(elapsedRealtime);
758         for (int i = 0; i < size; i++) {
759             pw.print(prefix);
760             pw.print("  ["); pw.print(i); pw.print("]");
761             pw.print(" iface="); pw.print(iface[i]);
762             pw.print(" uid="); pw.print(uid[i]);
763             pw.print(" set="); pw.print(setToString(set[i]));
764             pw.print(" tag="); pw.print(tagToString(tag[i]));
765             pw.print(" roaming="); pw.print(roamingToString(roaming[i]));
766             pw.print(" rxBytes="); pw.print(rxBytes[i]);
767             pw.print(" rxPackets="); pw.print(rxPackets[i]);
768             pw.print(" txBytes="); pw.print(txBytes[i]);
769             pw.print(" txPackets="); pw.print(txPackets[i]);
770             pw.print(" operations="); pw.println(operations[i]);
771         }
772     }
773 
774     /**
775      * Return text description of {@link #set} value.
776      */
setToString(int set)777     public static String setToString(int set) {
778         switch (set) {
779             case SET_ALL:
780                 return "ALL";
781             case SET_DEFAULT:
782                 return "DEFAULT";
783             case SET_FOREGROUND:
784                 return "FOREGROUND";
785             case SET_DBG_VPN_IN:
786                 return "DBG_VPN_IN";
787             case SET_DBG_VPN_OUT:
788                 return "DBG_VPN_OUT";
789             default:
790                 return "UNKNOWN";
791         }
792     }
793 
794     /**
795      * Return text description of {@link #set} value.
796      */
setToCheckinString(int set)797     public static String setToCheckinString(int set) {
798         switch (set) {
799             case SET_ALL:
800                 return "all";
801             case SET_DEFAULT:
802                 return "def";
803             case SET_FOREGROUND:
804                 return "fg";
805             case SET_DBG_VPN_IN:
806                 return "vpnin";
807             case SET_DBG_VPN_OUT:
808                 return "vpnout";
809             default:
810                 return "unk";
811         }
812     }
813 
814     /**
815      * @return true if the querySet matches the dataSet.
816      */
setMatches(int querySet, int dataSet)817     public static boolean setMatches(int querySet, int dataSet) {
818         if (querySet == dataSet) {
819             return true;
820         }
821         // SET_ALL matches all non-debugging sets.
822         return querySet == SET_ALL && dataSet < SET_DEBUG_START;
823     }
824 
825     /**
826      * Return text description of {@link #tag} value.
827      */
tagToString(int tag)828     public static String tagToString(int tag) {
829         return "0x" + Integer.toHexString(tag);
830     }
831 
832     /**
833      * Return text description of {@link #roaming} value.
834      */
roamingToString(int roaming)835     public static String roamingToString(int roaming) {
836         switch (roaming) {
837             case ROAMING_ALL:
838                 return "ALL";
839             case ROAMING_NO:
840                 return "NO";
841             case ROAMING_YES:
842                 return "YES";
843             default:
844                 return "UNKNOWN";
845         }
846     }
847 
848     @Override
toString()849     public String toString() {
850         final CharArrayWriter writer = new CharArrayWriter();
851         dump("", new PrintWriter(writer));
852         return writer.toString();
853     }
854 
855     @Override
describeContents()856     public int describeContents() {
857         return 0;
858     }
859 
860     public static final Creator<NetworkStats> CREATOR = new Creator<NetworkStats>() {
861         @Override
862         public NetworkStats createFromParcel(Parcel in) {
863             return new NetworkStats(in);
864         }
865 
866         @Override
867         public NetworkStats[] newArray(int size) {
868             return new NetworkStats[size];
869         }
870     };
871 
872     public interface NonMonotonicObserver<C> {
foundNonMonotonic( NetworkStats left, int leftIndex, NetworkStats right, int rightIndex, C cookie)873         public void foundNonMonotonic(
874                 NetworkStats left, int leftIndex, NetworkStats right, int rightIndex, C cookie);
875     }
876 
877     /**
878      * VPN accounting. Move some VPN's underlying traffic to other UIDs that use tun0 iface.
879      *
880      * This method should only be called on delta NetworkStats. Do not call this method on a
881      * snapshot {@link NetworkStats} object because the tunUid and/or the underlyingIface may
882      * change over time.
883      *
884      * This method performs adjustments for one active VPN package and one VPN iface at a time.
885      *
886      * It is possible for the VPN software to use multiple underlying networks. This method
887      * only migrates traffic for the primary underlying network.
888      *
889      * @param tunUid uid of the VPN application
890      * @param tunIface iface of the vpn tunnel
891      * @param underlyingIface the primary underlying network iface used by the VPN application
892      * @return true if it successfully adjusts the accounting for VPN, false otherwise
893      */
migrateTun(int tunUid, String tunIface, String underlyingIface)894     public boolean migrateTun(int tunUid, String tunIface, String underlyingIface) {
895         Entry tunIfaceTotal = new Entry();
896         Entry underlyingIfaceTotal = new Entry();
897 
898         tunAdjustmentInit(tunUid, tunIface, underlyingIface, tunIfaceTotal, underlyingIfaceTotal);
899 
900         // If tunIface < underlyingIface, it leaves the overhead traffic in the VPN app.
901         // If tunIface > underlyingIface, the VPN app doesn't get credit for data compression.
902         // Negative stats should be avoided.
903         Entry pool = tunGetPool(tunIfaceTotal, underlyingIfaceTotal);
904         if (pool.isEmpty()) {
905             return true;
906         }
907         Entry moved = addTrafficToApplications(tunIface,  underlyingIface, tunIfaceTotal, pool);
908         deductTrafficFromVpnApp(tunUid, underlyingIface, moved);
909 
910         if (!moved.isEmpty()) {
911             Slog.wtf(TAG, "Failed to deduct underlying network traffic from VPN package. Moved="
912                     + moved);
913             return false;
914         }
915         return true;
916     }
917 
918     /**
919      * Initializes the data used by the migrateTun() method.
920      *
921      * This is the first pass iteration which does the following work:
922      * (1) Adds up all the traffic through tun0.
923      * (2) Adds up all the traffic through the tunUid's underlyingIface
924      *     (both foreground and background).
925      */
tunAdjustmentInit(int tunUid, String tunIface, String underlyingIface, Entry tunIfaceTotal, Entry underlyingIfaceTotal)926     private void tunAdjustmentInit(int tunUid, String tunIface, String underlyingIface,
927             Entry tunIfaceTotal, Entry underlyingIfaceTotal) {
928         Entry recycle = new Entry();
929         for (int i = 0; i < size; i++) {
930             getValues(i, recycle);
931             if (recycle.uid == UID_ALL) {
932                 throw new IllegalStateException(
933                         "Cannot adjust VPN accounting on an iface aggregated NetworkStats.");
934             } if (recycle.set == SET_DBG_VPN_IN || recycle.set == SET_DBG_VPN_OUT) {
935                 throw new IllegalStateException(
936                         "Cannot adjust VPN accounting on a NetworkStats containing SET_DBG_VPN_*");
937             }
938 
939             if (recycle.uid == tunUid && recycle.tag == TAG_NONE
940                     && Objects.equals(underlyingIface, recycle.iface)) {
941                 underlyingIfaceTotal.add(recycle);
942             }
943 
944             if (recycle.tag == TAG_NONE && Objects.equals(tunIface, recycle.iface)) {
945                 // Add up all tunIface traffic.
946                 tunIfaceTotal.add(recycle);
947             }
948         }
949     }
950 
tunGetPool(Entry tunIfaceTotal, Entry underlyingIfaceTotal)951     private static Entry tunGetPool(Entry tunIfaceTotal, Entry underlyingIfaceTotal) {
952         Entry pool = new Entry();
953         pool.rxBytes = Math.min(tunIfaceTotal.rxBytes, underlyingIfaceTotal.rxBytes);
954         pool.rxPackets = Math.min(tunIfaceTotal.rxPackets, underlyingIfaceTotal.rxPackets);
955         pool.txBytes = Math.min(tunIfaceTotal.txBytes, underlyingIfaceTotal.txBytes);
956         pool.txPackets = Math.min(tunIfaceTotal.txPackets, underlyingIfaceTotal.txPackets);
957         pool.operations = Math.min(tunIfaceTotal.operations, underlyingIfaceTotal.operations);
958         return pool;
959     }
960 
addTrafficToApplications(String tunIface, String underlyingIface, Entry tunIfaceTotal, Entry pool)961     private Entry addTrafficToApplications(String tunIface, String underlyingIface,
962             Entry tunIfaceTotal, Entry pool) {
963         Entry moved = new Entry();
964         Entry tmpEntry = new Entry();
965         tmpEntry.iface = underlyingIface;
966         for (int i = 0; i < size; i++) {
967             if (Objects.equals(iface[i], tunIface)) {
968                 if (tunIfaceTotal.rxBytes > 0) {
969                     tmpEntry.rxBytes = pool.rxBytes * rxBytes[i] / tunIfaceTotal.rxBytes;
970                 } else {
971                     tmpEntry.rxBytes = 0;
972                 }
973                 if (tunIfaceTotal.rxPackets > 0) {
974                     tmpEntry.rxPackets = pool.rxPackets * rxPackets[i] / tunIfaceTotal.rxPackets;
975                 } else {
976                     tmpEntry.rxPackets = 0;
977                 }
978                 if (tunIfaceTotal.txBytes > 0) {
979                     tmpEntry.txBytes = pool.txBytes * txBytes[i] / tunIfaceTotal.txBytes;
980                 } else {
981                     tmpEntry.txBytes = 0;
982                 }
983                 if (tunIfaceTotal.txPackets > 0) {
984                     tmpEntry.txPackets = pool.txPackets * txPackets[i] / tunIfaceTotal.txPackets;
985                 } else {
986                     tmpEntry.txPackets = 0;
987                 }
988                 if (tunIfaceTotal.operations > 0) {
989                     tmpEntry.operations =
990                             pool.operations * operations[i] / tunIfaceTotal.operations;
991                 } else {
992                     tmpEntry.operations = 0;
993                 }
994                 tmpEntry.uid = uid[i];
995                 tmpEntry.tag = tag[i];
996                 tmpEntry.set = set[i];
997                 tmpEntry.roaming = roaming[i];
998                 combineValues(tmpEntry);
999                 if (tag[i] == TAG_NONE) {
1000                     moved.add(tmpEntry);
1001                     // Add debug info
1002                     tmpEntry.set = SET_DBG_VPN_IN;
1003                     combineValues(tmpEntry);
1004                 }
1005             }
1006         }
1007         return moved;
1008     }
1009 
deductTrafficFromVpnApp(int tunUid, String underlyingIface, Entry moved)1010     private void deductTrafficFromVpnApp(int tunUid, String underlyingIface, Entry moved) {
1011         // Add debug info
1012         moved.uid = tunUid;
1013         moved.set = SET_DBG_VPN_OUT;
1014         moved.tag = TAG_NONE;
1015         moved.iface = underlyingIface;
1016         moved.roaming = ROAMING_ALL;
1017         combineValues(moved);
1018 
1019         // Caveat: if the vpn software uses tag, the total tagged traffic may be greater than
1020         // the TAG_NONE traffic.
1021         //
1022         // Relies on the fact that the underlying traffic only has state ROAMING_NO, which
1023         // should be the case as it comes directly from the /proc file. We only blend in the
1024         // roaming data after applying these adjustments, by checking the NetworkIdentity of the
1025         // underlying iface.
1026         int idxVpnBackground = findIndex(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE,
1027                 ROAMING_NO);
1028         if (idxVpnBackground != -1) {
1029             tunSubtract(idxVpnBackground, this, moved);
1030         }
1031 
1032         int idxVpnForeground = findIndex(underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE,
1033                 ROAMING_NO);
1034         if (idxVpnForeground != -1) {
1035             tunSubtract(idxVpnForeground, this, moved);
1036         }
1037     }
1038 
tunSubtract(int i, NetworkStats left, Entry right)1039     private static void tunSubtract(int i, NetworkStats left, Entry right) {
1040         long rxBytes = Math.min(left.rxBytes[i], right.rxBytes);
1041         left.rxBytes[i] -= rxBytes;
1042         right.rxBytes -= rxBytes;
1043 
1044         long rxPackets = Math.min(left.rxPackets[i], right.rxPackets);
1045         left.rxPackets[i] -= rxPackets;
1046         right.rxPackets -= rxPackets;
1047 
1048         long txBytes = Math.min(left.txBytes[i], right.txBytes);
1049         left.txBytes[i] -= txBytes;
1050         right.txBytes -= txBytes;
1051 
1052         long txPackets = Math.min(left.txPackets[i], right.txPackets);
1053         left.txPackets[i] -= txPackets;
1054         right.txPackets -= txPackets;
1055     }
1056 }
1057