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