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