OSDN Git Service

Make ConnectivityMetricsLogger and related classes @SystemApi
[android-x86/frameworks-base.git] / core / java / android / net / NetworkStats.java
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
115         public Entry() {
116             this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
117         }
118
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
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
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
144         public boolean isNegative() {
145             return rxBytes < 0 || rxPackets < 0 || txBytes < 0 || txPackets < 0 || operations < 0;
146         }
147
148         public boolean isEmpty() {
149             return rxBytes == 0 && rxPackets == 0 && txBytes == 0 && txPackets == 0
150                     && operations == 0;
151         }
152
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
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
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
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
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
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
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
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
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
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      */
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      */
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
339     public long getElapsedRealtime() {
340         return elapsedRealtime;
341     }
342
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      */
351     public long getElapsedRealtimeAge() {
352         return SystemClock.elapsedRealtime() - elapsedRealtime;
353     }
354
355     public int size() {
356         return size;
357     }
358
359     @VisibleForTesting
360     public int internalSize() {
361         return capacity;
362     }
363
364     @Deprecated
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
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      */
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      */
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      */
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
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      */
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      */
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      */
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      */
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      */
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      */
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      */
522     public Entry getTotal(Entry recycle, HashSet<String> limitIface) {
523         return getTotal(recycle, limitIface, UID_ALL, false);
524     }
525
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      */
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      */
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      */
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      */
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      */
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      */
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      */
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      */
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
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      */
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      */
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      */
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      */
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      */
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
849     public String toString() {
850         final CharArrayWriter writer = new CharArrayWriter();
851         dump("", new PrintWriter(writer));
852         return writer.toString();
853     }
854
855     @Override
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> {
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      */
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      */
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
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
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
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
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 }