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    // 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 =
908                addTrafficToApplications(tunUid, tunIface, underlyingIface, tunIfaceTotal, pool);
909        deductTrafficFromVpnApp(tunUid, underlyingIface, moved);
910
911        if (!moved.isEmpty()) {
912            Slog.wtf(TAG, "Failed to deduct underlying network traffic from VPN package. Moved="
913                    + moved);
914            return false;
915        }
916        return true;
917    }
918
919    /**
920     * Initializes the data used by the migrateTun() method.
921     *
922     * This is the first pass iteration which does the following work:
923     * (1) Adds up all the traffic through the tunUid's underlyingIface
924     *     (both foreground and background).
925     * (2) Adds up all the traffic through tun0 excluding traffic from the vpn app itself.
926     */
927    private void tunAdjustmentInit(int tunUid, String tunIface, String underlyingIface,
928            Entry tunIfaceTotal, Entry underlyingIfaceTotal) {
929        Entry recycle = new Entry();
930        for (int i = 0; i < size; i++) {
931            getValues(i, recycle);
932            if (recycle.uid == UID_ALL) {
933                throw new IllegalStateException(
934                        "Cannot adjust VPN accounting on an iface aggregated NetworkStats.");
935            } if (recycle.set == SET_DBG_VPN_IN || recycle.set == SET_DBG_VPN_OUT) {
936                throw new IllegalStateException(
937                        "Cannot adjust VPN accounting on a NetworkStats containing SET_DBG_VPN_*");
938            }
939
940            if (recycle.uid == tunUid && recycle.tag == TAG_NONE
941                    && Objects.equals(underlyingIface, recycle.iface)) {
942                underlyingIfaceTotal.add(recycle);
943            }
944
945            if (recycle.uid != tunUid && recycle.tag == TAG_NONE
946                    && Objects.equals(tunIface, recycle.iface)) {
947                // Add up all tunIface traffic excluding traffic from the vpn app itself.
948                tunIfaceTotal.add(recycle);
949            }
950        }
951    }
952
953    private static Entry tunGetPool(Entry tunIfaceTotal, Entry underlyingIfaceTotal) {
954        Entry pool = new Entry();
955        pool.rxBytes = Math.min(tunIfaceTotal.rxBytes, underlyingIfaceTotal.rxBytes);
956        pool.rxPackets = Math.min(tunIfaceTotal.rxPackets, underlyingIfaceTotal.rxPackets);
957        pool.txBytes = Math.min(tunIfaceTotal.txBytes, underlyingIfaceTotal.txBytes);
958        pool.txPackets = Math.min(tunIfaceTotal.txPackets, underlyingIfaceTotal.txPackets);
959        pool.operations = Math.min(tunIfaceTotal.operations, underlyingIfaceTotal.operations);
960        return pool;
961    }
962
963    private Entry addTrafficToApplications(int tunUid, String tunIface, String underlyingIface,
964            Entry tunIfaceTotal, Entry pool) {
965        Entry moved = new Entry();
966        Entry tmpEntry = new Entry();
967        tmpEntry.iface = underlyingIface;
968        for (int i = 0; i < size; i++) {
969            // the vpn app is excluded from the redistribution but all moved traffic will be
970            // deducted from the vpn app (see deductTrafficFromVpnApp below).
971            if (Objects.equals(iface[i], tunIface) && uid[i] != tunUid) {
972                if (tunIfaceTotal.rxBytes > 0) {
973                    tmpEntry.rxBytes = pool.rxBytes * rxBytes[i] / tunIfaceTotal.rxBytes;
974                } else {
975                    tmpEntry.rxBytes = 0;
976                }
977                if (tunIfaceTotal.rxPackets > 0) {
978                    tmpEntry.rxPackets = pool.rxPackets * rxPackets[i] / tunIfaceTotal.rxPackets;
979                } else {
980                    tmpEntry.rxPackets = 0;
981                }
982                if (tunIfaceTotal.txBytes > 0) {
983                    tmpEntry.txBytes = pool.txBytes * txBytes[i] / tunIfaceTotal.txBytes;
984                } else {
985                    tmpEntry.txBytes = 0;
986                }
987                if (tunIfaceTotal.txPackets > 0) {
988                    tmpEntry.txPackets = pool.txPackets * txPackets[i] / tunIfaceTotal.txPackets;
989                } else {
990                    tmpEntry.txPackets = 0;
991                }
992                if (tunIfaceTotal.operations > 0) {
993                    tmpEntry.operations =
994                            pool.operations * operations[i] / tunIfaceTotal.operations;
995                } else {
996                    tmpEntry.operations = 0;
997                }
998                tmpEntry.uid = uid[i];
999                tmpEntry.tag = tag[i];
1000                tmpEntry.set = set[i];
1001                tmpEntry.roaming = roaming[i];
1002                combineValues(tmpEntry);
1003                if (tag[i] == TAG_NONE) {
1004                    moved.add(tmpEntry);
1005                    // Add debug info
1006                    tmpEntry.set = SET_DBG_VPN_IN;
1007                    combineValues(tmpEntry);
1008                }
1009            }
1010        }
1011        return moved;
1012    }
1013
1014    private void deductTrafficFromVpnApp(int tunUid, String underlyingIface, Entry moved) {
1015        // Add debug info
1016        moved.uid = tunUid;
1017        moved.set = SET_DBG_VPN_OUT;
1018        moved.tag = TAG_NONE;
1019        moved.iface = underlyingIface;
1020        moved.roaming = ROAMING_ALL;
1021        combineValues(moved);
1022
1023        // Caveat: if the vpn software uses tag, the total tagged traffic may be greater than
1024        // the TAG_NONE traffic.
1025        //
1026        // Relies on the fact that the underlying traffic only has state ROAMING_NO, which
1027        // should be the case as it comes directly from the /proc file. We only blend in the
1028        // roaming data after applying these adjustments, by checking the NetworkIdentity of the
1029        // underlying iface.
1030        int idxVpnBackground = findIndex(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE,
1031                ROAMING_NO);
1032        if (idxVpnBackground != -1) {
1033            tunSubtract(idxVpnBackground, this, moved);
1034        }
1035
1036        int idxVpnForeground = findIndex(underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE,
1037                ROAMING_NO);
1038        if (idxVpnForeground != -1) {
1039            tunSubtract(idxVpnForeground, this, moved);
1040        }
1041    }
1042
1043    private static void tunSubtract(int i, NetworkStats left, Entry right) {
1044        long rxBytes = Math.min(left.rxBytes[i], right.rxBytes);
1045        left.rxBytes[i] -= rxBytes;
1046        right.rxBytes -= rxBytes;
1047
1048        long rxPackets = Math.min(left.rxPackets[i], right.rxPackets);
1049        left.rxPackets[i] -= rxPackets;
1050        right.rxPackets -= rxPackets;
1051
1052        long txBytes = Math.min(left.txBytes[i], right.txBytes);
1053        left.txBytes[i] -= txBytes;
1054        right.txBytes -= txBytes;
1055
1056        long txPackets = Math.min(left.txPackets[i], right.txPackets);
1057        left.txPackets[i] -= txPackets;
1058        right.txPackets -= txPackets;
1059    }
1060}
1061