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