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