NetworkStats.java revision 75525b39f4deb8409825fd43195d5f82628ef3e5
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.Map;
35import java.util.Objects;
36
37/**
38 * Collection of active network statistics. Can contain summary details across
39 * all interfaces, or details with per-UID granularity. Internally stores data
40 * as a large table, closely matching {@code /proc/} data format. This structure
41 * optimizes for rapid in-memory comparison, but consider using
42 * {@link NetworkStatsHistory} when persisting.
43 *
44 * @hide
45 */
46public class NetworkStats implements Parcelable {
47    private static final String TAG = "NetworkStats";
48    /** {@link #iface} value when interface details unavailable. */
49    public static final String IFACE_ALL = null;
50    /** {@link #uid} value when UID details unavailable. */
51    public static final int UID_ALL = -1;
52    /** {@link #tag} value matching any tag. */
53    // TODO: Rename TAG_ALL to TAG_ANY.
54    public static final int TAG_ALL = -1;
55    /** {@link #set} value for all sets combined, not including debug sets. */
56    public static final int SET_ALL = -1;
57    /** {@link #set} value where background data is accounted. */
58    public static final int SET_DEFAULT = 0;
59    /** {@link #set} value where foreground data is accounted. */
60    public static final int SET_FOREGROUND = 1;
61    /** All {@link #set} value greater than SET_DEBUG_START are debug {@link #set} values. */
62    public static final int SET_DEBUG_START = 1000;
63    /** Debug {@link #set} value when the VPN stats are moved in. */
64    public static final int SET_DBG_VPN_IN = 1001;
65    /** Debug {@link #set} value when the VPN stats are moved out of a vpn UID. */
66    public static final int SET_DBG_VPN_OUT = 1002;
67
68    /** Include all interfaces when filtering */
69    public static final String[] INTERFACES_ALL = null;
70
71    /** {@link #tag} value for total data across all tags. */
72    // TODO: Rename TAG_NONE to TAG_ALL.
73    public static final int TAG_NONE = 0;
74
75    /** {@link #metered} value to account for all metered states. */
76    public static final int METERED_ALL = -1;
77    /** {@link #metered} value where native, unmetered data is accounted. */
78    public static final int METERED_NO = 0;
79    /** {@link #metered} value where metered data is accounted. */
80    public static final int METERED_YES = 1;
81
82    /** {@link #roaming} value to account for all roaming states. */
83    public static final int ROAMING_ALL = -1;
84    /** {@link #roaming} value where native, non-roaming data is accounted. */
85    public static final int ROAMING_NO = 0;
86    /** {@link #roaming} value where roaming data is accounted. */
87    public static final int ROAMING_YES = 1;
88
89    /** {@link #onDefaultNetwork} value to account for all default network states. */
90    public static final int DEFAULT_NETWORK_ALL = -1;
91    /** {@link #onDefaultNetwork} value to account for usage while not the default network. */
92    public static final int DEFAULT_NETWORK_NO = 0;
93    /** {@link #onDefaultNetwork} value to account for usage while the default network. */
94    public static final int DEFAULT_NETWORK_YES = 1;
95
96    /** Denotes a request for stats at the interface level. */
97    public static final int STATS_PER_IFACE = 0;
98    /** Denotes a request for stats at the interface and UID level. */
99    public static final int STATS_PER_UID = 1;
100
101    private static final String CLATD_INTERFACE_PREFIX = "v4-";
102    // Delta between IPv4 header (20b) and IPv6 header (40b).
103    // Used for correct stats accounting on clatd interfaces.
104    private static final int IPV4V6_HEADER_DELTA = 20;
105
106    // TODO: move fields to "mVariable" notation
107
108    /**
109     * {@link SystemClock#elapsedRealtime()} timestamp when this data was
110     * generated.
111     */
112    private long elapsedRealtime;
113    private int size;
114    private int capacity;
115    private String[] iface;
116    private int[] uid;
117    private int[] set;
118    private int[] tag;
119    private int[] metered;
120    private int[] roaming;
121    private int[] defaultNetwork;
122    private long[] rxBytes;
123    private long[] rxPackets;
124    private long[] txBytes;
125    private long[] txPackets;
126    private long[] operations;
127
128    public static class Entry {
129        public String iface;
130        public int uid;
131        public int set;
132        public int tag;
133        /**
134         * Note that this is only populated w/ the default value when read from /proc or written
135         * to disk. We merge in the correct value when reporting this value to clients of
136         * getSummary().
137         */
138        public int metered;
139        /**
140         * Note that this is only populated w/ the default value when read from /proc or written
141         * to disk. We merge in the correct value when reporting this value to clients of
142         * getSummary().
143         */
144        public int roaming;
145        /**
146         * Note that this is only populated w/ the default value when read from /proc or written
147         * to disk. We merge in the correct value when reporting this value to clients of
148         * getSummary().
149         */
150        public int defaultNetwork;
151        public long rxBytes;
152        public long rxPackets;
153        public long txBytes;
154        public long txPackets;
155        public long operations;
156
157        public Entry() {
158            this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
159        }
160
161        public Entry(long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) {
162            this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets,
163                    operations);
164        }
165
166        public Entry(String iface, int uid, int set, int tag, long rxBytes, long rxPackets,
167                long txBytes, long txPackets, long operations) {
168            this(iface, uid, set, tag, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO,
169                    rxBytes, rxPackets, txBytes, txPackets, operations);
170        }
171
172        public Entry(String iface, int uid, int set, int tag, int metered, int roaming,
173                 int defaultNetwork, long rxBytes, long rxPackets, long txBytes, long txPackets,
174                 long operations) {
175            this.iface = iface;
176            this.uid = uid;
177            this.set = set;
178            this.tag = tag;
179            this.metered = metered;
180            this.roaming = roaming;
181            this.defaultNetwork = defaultNetwork;
182            this.rxBytes = rxBytes;
183            this.rxPackets = rxPackets;
184            this.txBytes = txBytes;
185            this.txPackets = txPackets;
186            this.operations = operations;
187        }
188
189        public boolean isNegative() {
190            return rxBytes < 0 || rxPackets < 0 || txBytes < 0 || txPackets < 0 || operations < 0;
191        }
192
193        public boolean isEmpty() {
194            return rxBytes == 0 && rxPackets == 0 && txBytes == 0 && txPackets == 0
195                    && operations == 0;
196        }
197
198        public void add(Entry another) {
199            this.rxBytes += another.rxBytes;
200            this.rxPackets += another.rxPackets;
201            this.txBytes += another.txBytes;
202            this.txPackets += another.txPackets;
203            this.operations += another.operations;
204        }
205
206        @Override
207        public String toString() {
208            final StringBuilder builder = new StringBuilder();
209            builder.append("iface=").append(iface);
210            builder.append(" uid=").append(uid);
211            builder.append(" set=").append(setToString(set));
212            builder.append(" tag=").append(tagToString(tag));
213            builder.append(" metered=").append(meteredToString(metered));
214            builder.append(" roaming=").append(roamingToString(roaming));
215            builder.append(" defaultNetwork=").append(defaultNetworkToString(defaultNetwork));
216            builder.append(" rxBytes=").append(rxBytes);
217            builder.append(" rxPackets=").append(rxPackets);
218            builder.append(" txBytes=").append(txBytes);
219            builder.append(" txPackets=").append(txPackets);
220            builder.append(" operations=").append(operations);
221            return builder.toString();
222        }
223
224        @Override
225        public boolean equals(Object o) {
226            if (o instanceof Entry) {
227                final Entry e = (Entry) o;
228                return uid == e.uid && set == e.set && tag == e.tag && metered == e.metered
229                        && roaming == e.roaming && defaultNetwork == e.defaultNetwork
230                        && rxBytes == e.rxBytes && rxPackets == e.rxPackets
231                        && txBytes == e.txBytes && txPackets == e.txPackets
232                        && operations == e.operations && iface.equals(e.iface);
233            }
234            return false;
235        }
236
237        @Override
238        public int hashCode() {
239            return Objects.hash(uid, set, tag, metered, roaming, defaultNetwork, iface);
240        }
241    }
242
243    public NetworkStats(long elapsedRealtime, int initialSize) {
244        this.elapsedRealtime = elapsedRealtime;
245        this.size = 0;
246        if (initialSize > 0) {
247            this.capacity = initialSize;
248            this.iface = new String[initialSize];
249            this.uid = new int[initialSize];
250            this.set = new int[initialSize];
251            this.tag = new int[initialSize];
252            this.metered = new int[initialSize];
253            this.roaming = new int[initialSize];
254            this.defaultNetwork = new int[initialSize];
255            this.rxBytes = new long[initialSize];
256            this.rxPackets = new long[initialSize];
257            this.txBytes = new long[initialSize];
258            this.txPackets = new long[initialSize];
259            this.operations = new long[initialSize];
260        } else {
261            // Special case for use by NetworkStatsFactory to start out *really* empty.
262            clear();
263        }
264    }
265
266    public NetworkStats(Parcel parcel) {
267        elapsedRealtime = parcel.readLong();
268        size = parcel.readInt();
269        capacity = parcel.readInt();
270        iface = parcel.createStringArray();
271        uid = parcel.createIntArray();
272        set = parcel.createIntArray();
273        tag = parcel.createIntArray();
274        metered = parcel.createIntArray();
275        roaming = parcel.createIntArray();
276        defaultNetwork = parcel.createIntArray();
277        rxBytes = parcel.createLongArray();
278        rxPackets = parcel.createLongArray();
279        txBytes = parcel.createLongArray();
280        txPackets = parcel.createLongArray();
281        operations = parcel.createLongArray();
282    }
283
284    @Override
285    public void writeToParcel(Parcel dest, int flags) {
286        dest.writeLong(elapsedRealtime);
287        dest.writeInt(size);
288        dest.writeInt(capacity);
289        dest.writeStringArray(iface);
290        dest.writeIntArray(uid);
291        dest.writeIntArray(set);
292        dest.writeIntArray(tag);
293        dest.writeIntArray(metered);
294        dest.writeIntArray(roaming);
295        dest.writeIntArray(defaultNetwork);
296        dest.writeLongArray(rxBytes);
297        dest.writeLongArray(rxPackets);
298        dest.writeLongArray(txBytes);
299        dest.writeLongArray(txPackets);
300        dest.writeLongArray(operations);
301    }
302
303    @Override
304    public NetworkStats clone() {
305        final NetworkStats clone = new NetworkStats(elapsedRealtime, size);
306        NetworkStats.Entry entry = null;
307        for (int i = 0; i < size; i++) {
308            entry = getValues(i, entry);
309            clone.addValues(entry);
310        }
311        return clone;
312    }
313
314    /**
315     * Clear all data stored in this object.
316     */
317    public void clear() {
318        this.capacity = 0;
319        this.iface = EmptyArray.STRING;
320        this.uid = EmptyArray.INT;
321        this.set = EmptyArray.INT;
322        this.tag = EmptyArray.INT;
323        this.metered = EmptyArray.INT;
324        this.roaming = EmptyArray.INT;
325        this.defaultNetwork = EmptyArray.INT;
326        this.rxBytes = EmptyArray.LONG;
327        this.rxPackets = EmptyArray.LONG;
328        this.txBytes = EmptyArray.LONG;
329        this.txPackets = EmptyArray.LONG;
330        this.operations = EmptyArray.LONG;
331    }
332
333    @VisibleForTesting
334    public NetworkStats addIfaceValues(
335            String iface, long rxBytes, long rxPackets, long txBytes, long txPackets) {
336        return addValues(
337                iface, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets, 0L);
338    }
339
340    @VisibleForTesting
341    public NetworkStats addValues(String iface, int uid, int set, int tag, long rxBytes,
342            long rxPackets, long txBytes, long txPackets, long operations) {
343        return addValues(new Entry(
344                iface, uid, set, tag, rxBytes, rxPackets, txBytes, txPackets, operations));
345    }
346
347    @VisibleForTesting
348    public NetworkStats addValues(String iface, int uid, int set, int tag, int metered, int roaming,
349            int defaultNetwork, long rxBytes, long rxPackets, long txBytes, long txPackets,
350            long operations) {
351        return addValues(new Entry(
352                iface, uid, set, tag, metered, roaming, defaultNetwork, rxBytes, rxPackets,
353                txBytes, txPackets, operations));
354    }
355
356    /**
357     * Add new stats entry, copying from given {@link Entry}. The {@link Entry}
358     * object can be recycled across multiple calls.
359     */
360    public NetworkStats addValues(Entry entry) {
361        if (size >= capacity) {
362            final int newLength = Math.max(size, 10) * 3 / 2;
363            iface = Arrays.copyOf(iface, newLength);
364            uid = Arrays.copyOf(uid, newLength);
365            set = Arrays.copyOf(set, newLength);
366            tag = Arrays.copyOf(tag, newLength);
367            metered = Arrays.copyOf(metered, newLength);
368            roaming = Arrays.copyOf(roaming, newLength);
369            defaultNetwork = Arrays.copyOf(defaultNetwork, newLength);
370            rxBytes = Arrays.copyOf(rxBytes, newLength);
371            rxPackets = Arrays.copyOf(rxPackets, newLength);
372            txBytes = Arrays.copyOf(txBytes, newLength);
373            txPackets = Arrays.copyOf(txPackets, newLength);
374            operations = Arrays.copyOf(operations, newLength);
375            capacity = newLength;
376        }
377
378        setValues(size, entry);
379        size++;
380
381        return this;
382    }
383
384    private void setValues(int i, Entry entry) {
385        iface[i] = entry.iface;
386        uid[i] = entry.uid;
387        set[i] = entry.set;
388        tag[i] = entry.tag;
389        metered[i] = entry.metered;
390        roaming[i] = entry.roaming;
391        defaultNetwork[i] = entry.defaultNetwork;
392        rxBytes[i] = entry.rxBytes;
393        rxPackets[i] = entry.rxPackets;
394        txBytes[i] = entry.txBytes;
395        txPackets[i] = entry.txPackets;
396        operations[i] = entry.operations;
397    }
398
399    /**
400     * Return specific stats entry.
401     */
402    public Entry getValues(int i, Entry recycle) {
403        final Entry entry = recycle != null ? recycle : new Entry();
404        entry.iface = iface[i];
405        entry.uid = uid[i];
406        entry.set = set[i];
407        entry.tag = tag[i];
408        entry.metered = metered[i];
409        entry.roaming = roaming[i];
410        entry.defaultNetwork = defaultNetwork[i];
411        entry.rxBytes = rxBytes[i];
412        entry.rxPackets = rxPackets[i];
413        entry.txBytes = txBytes[i];
414        entry.txPackets = txPackets[i];
415        entry.operations = operations[i];
416        return entry;
417    }
418
419    public long getElapsedRealtime() {
420        return elapsedRealtime;
421    }
422
423    public void setElapsedRealtime(long time) {
424        elapsedRealtime = time;
425    }
426
427    /**
428     * Return age of this {@link NetworkStats} object with respect to
429     * {@link SystemClock#elapsedRealtime()}.
430     */
431    public long getElapsedRealtimeAge() {
432        return SystemClock.elapsedRealtime() - elapsedRealtime;
433    }
434
435    public int size() {
436        return size;
437    }
438
439    @VisibleForTesting
440    public int internalSize() {
441        return capacity;
442    }
443
444    @Deprecated
445    public NetworkStats combineValues(String iface, int uid, int tag, long rxBytes, long rxPackets,
446            long txBytes, long txPackets, long operations) {
447        return combineValues(
448                iface, uid, SET_DEFAULT, tag, rxBytes, rxPackets, txBytes,
449                txPackets, operations);
450    }
451
452    public NetworkStats combineValues(String iface, int uid, int set, int tag,
453            long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) {
454        return combineValues(new Entry(
455                iface, uid, set, tag, rxBytes, rxPackets, txBytes, txPackets, operations));
456    }
457
458    /**
459     * Combine given values with an existing row, or create a new row if
460     * {@link #findIndex(String, int, int, int, int)} is unable to find match. Can
461     * also be used to subtract values from existing rows.
462     */
463    public NetworkStats combineValues(Entry entry) {
464        final int i = findIndex(entry.iface, entry.uid, entry.set, entry.tag, entry.metered,
465                entry.roaming, entry.defaultNetwork);
466        if (i == -1) {
467            // only create new entry when positive contribution
468            addValues(entry);
469        } else {
470            rxBytes[i] += entry.rxBytes;
471            rxPackets[i] += entry.rxPackets;
472            txBytes[i] += entry.txBytes;
473            txPackets[i] += entry.txPackets;
474            operations[i] += entry.operations;
475        }
476        return this;
477    }
478
479    /**
480     * Combine all values from another {@link NetworkStats} into this object.
481     */
482    public void combineAllValues(NetworkStats another) {
483        NetworkStats.Entry entry = null;
484        for (int i = 0; i < another.size; i++) {
485            entry = another.getValues(i, entry);
486            combineValues(entry);
487        }
488    }
489
490    /**
491     * Find first stats index that matches the requested parameters.
492     */
493    public int findIndex(String iface, int uid, int set, int tag, int metered, int roaming,
494            int defaultNetwork) {
495        for (int i = 0; i < size; i++) {
496            if (uid == this.uid[i] && set == this.set[i] && tag == this.tag[i]
497                    && metered == this.metered[i] && roaming == this.roaming[i]
498                    && defaultNetwork == this.defaultNetwork[i]
499                    && Objects.equals(iface, this.iface[i])) {
500                return i;
501            }
502        }
503        return -1;
504    }
505
506    /**
507     * Find first stats index that matches the requested parameters, starting
508     * search around the hinted index as an optimization.
509     */
510    @VisibleForTesting
511    public int findIndexHinted(String iface, int uid, int set, int tag, int metered, int roaming,
512            int defaultNetwork, int hintIndex) {
513        for (int offset = 0; offset < size; offset++) {
514            final int halfOffset = offset / 2;
515
516            // search outwards from hint index, alternating forward and backward
517            final int i;
518            if (offset % 2 == 0) {
519                i = (hintIndex + halfOffset) % size;
520            } else {
521                i = (size + hintIndex - halfOffset - 1) % size;
522            }
523
524            if (uid == this.uid[i] && set == this.set[i] && tag == this.tag[i]
525                    && metered == this.metered[i] && roaming == this.roaming[i]
526                    && defaultNetwork == this.defaultNetwork[i]
527                    && Objects.equals(iface, this.iface[i])) {
528                return i;
529            }
530        }
531        return -1;
532    }
533
534    /**
535     * Splice in {@link #operations} from the given {@link NetworkStats} based
536     * on matching {@link #uid} and {@link #tag} rows. Ignores {@link #iface},
537     * since operation counts are at data layer.
538     */
539    public void spliceOperationsFrom(NetworkStats stats) {
540        for (int i = 0; i < size; i++) {
541            final int j = stats.findIndex(iface[i], uid[i], set[i], tag[i], metered[i], roaming[i],
542                    defaultNetwork[i]);
543            if (j == -1) {
544                operations[i] = 0;
545            } else {
546                operations[i] = stats.operations[j];
547            }
548        }
549    }
550
551    /**
552     * Return list of unique interfaces known by this data structure.
553     */
554    public String[] getUniqueIfaces() {
555        final HashSet<String> ifaces = new HashSet<String>();
556        for (String iface : this.iface) {
557            if (iface != IFACE_ALL) {
558                ifaces.add(iface);
559            }
560        }
561        return ifaces.toArray(new String[ifaces.size()]);
562    }
563
564    /**
565     * Return list of unique UIDs known by this data structure.
566     */
567    public int[] getUniqueUids() {
568        final SparseBooleanArray uids = new SparseBooleanArray();
569        for (int uid : this.uid) {
570            uids.put(uid, true);
571        }
572
573        final int size = uids.size();
574        final int[] result = new int[size];
575        for (int i = 0; i < size; i++) {
576            result[i] = uids.keyAt(i);
577        }
578        return result;
579    }
580
581    /**
582     * Return total bytes represented by this snapshot object, usually used when
583     * checking if a {@link #subtract(NetworkStats)} delta passes a threshold.
584     */
585    public long getTotalBytes() {
586        final Entry entry = getTotal(null);
587        return entry.rxBytes + entry.txBytes;
588    }
589
590    /**
591     * Return total of all fields represented by this snapshot object.
592     */
593    public Entry getTotal(Entry recycle) {
594        return getTotal(recycle, null, UID_ALL, false);
595    }
596
597    /**
598     * Return total of all fields represented by this snapshot object matching
599     * the requested {@link #uid}.
600     */
601    public Entry getTotal(Entry recycle, int limitUid) {
602        return getTotal(recycle, null, limitUid, false);
603    }
604
605    /**
606     * Return total of all fields represented by this snapshot object matching
607     * the requested {@link #iface}.
608     */
609    public Entry getTotal(Entry recycle, HashSet<String> limitIface) {
610        return getTotal(recycle, limitIface, UID_ALL, false);
611    }
612
613    public Entry getTotalIncludingTags(Entry recycle) {
614        return getTotal(recycle, null, UID_ALL, true);
615    }
616
617    /**
618     * Return total of all fields represented by this snapshot object matching
619     * the requested {@link #iface} and {@link #uid}.
620     *
621     * @param limitIface Set of {@link #iface} to include in total; or {@code
622     *            null} to include all ifaces.
623     */
624    private Entry getTotal(
625            Entry recycle, HashSet<String> limitIface, int limitUid, boolean includeTags) {
626        final Entry entry = recycle != null ? recycle : new Entry();
627
628        entry.iface = IFACE_ALL;
629        entry.uid = limitUid;
630        entry.set = SET_ALL;
631        entry.tag = TAG_NONE;
632        entry.metered = METERED_ALL;
633        entry.roaming = ROAMING_ALL;
634        entry.defaultNetwork = DEFAULT_NETWORK_ALL;
635        entry.rxBytes = 0;
636        entry.rxPackets = 0;
637        entry.txBytes = 0;
638        entry.txPackets = 0;
639        entry.operations = 0;
640
641        for (int i = 0; i < size; i++) {
642            final boolean matchesUid = (limitUid == UID_ALL) || (limitUid == uid[i]);
643            final boolean matchesIface = (limitIface == null) || (limitIface.contains(iface[i]));
644
645            if (matchesUid && matchesIface) {
646                // skip specific tags, since already counted in TAG_NONE
647                if (tag[i] != TAG_NONE && !includeTags) continue;
648
649                entry.rxBytes += rxBytes[i];
650                entry.rxPackets += rxPackets[i];
651                entry.txBytes += txBytes[i];
652                entry.txPackets += txPackets[i];
653                entry.operations += operations[i];
654            }
655        }
656        return entry;
657    }
658
659    /**
660     * Fast path for battery stats.
661     */
662    public long getTotalPackets() {
663        long total = 0;
664        for (int i = size-1; i >= 0; i--) {
665            total += rxPackets[i] + txPackets[i];
666        }
667        return total;
668    }
669
670    /**
671     * Subtract the given {@link NetworkStats}, effectively leaving the delta
672     * between two snapshots in time. Assumes that statistics rows collect over
673     * time, and that none of them have disappeared.
674     */
675    public NetworkStats subtract(NetworkStats right) {
676        return subtract(this, right, null, null);
677    }
678
679    /**
680     * Subtract the two given {@link NetworkStats} objects, returning the delta
681     * between two snapshots in time. Assumes that statistics rows collect over
682     * time, and that none of them have disappeared.
683     * <p>
684     * If counters have rolled backwards, they are clamped to {@code 0} and
685     * reported to the given {@link NonMonotonicObserver}.
686     */
687    public static <C> NetworkStats subtract(NetworkStats left, NetworkStats right,
688            NonMonotonicObserver<C> observer, C cookie) {
689        return subtract(left, right, observer, cookie, null);
690    }
691
692    /**
693     * Subtract the two given {@link NetworkStats} objects, returning the delta
694     * between two snapshots in time. Assumes that statistics rows collect over
695     * time, and that none of them have disappeared.
696     * <p>
697     * If counters have rolled backwards, they are clamped to {@code 0} and
698     * reported to the given {@link NonMonotonicObserver}.
699     * <p>
700     * If <var>recycle</var> is supplied, this NetworkStats object will be
701     * reused (and returned) as the result if it is large enough to contain
702     * the data.
703     */
704    public static <C> NetworkStats subtract(NetworkStats left, NetworkStats right,
705            NonMonotonicObserver<C> observer, C cookie, NetworkStats recycle) {
706        long deltaRealtime = left.elapsedRealtime - right.elapsedRealtime;
707        if (deltaRealtime < 0) {
708            if (observer != null) {
709                observer.foundNonMonotonic(left, -1, right, -1, cookie);
710            }
711            deltaRealtime = 0;
712        }
713
714        // result will have our rows, and elapsed time between snapshots
715        final Entry entry = new Entry();
716        final NetworkStats result;
717        if (recycle != null && recycle.capacity >= left.size) {
718            result = recycle;
719            result.size = 0;
720            result.elapsedRealtime = deltaRealtime;
721        } else {
722            result = new NetworkStats(deltaRealtime, left.size);
723        }
724        for (int i = 0; i < left.size; i++) {
725            entry.iface = left.iface[i];
726            entry.uid = left.uid[i];
727            entry.set = left.set[i];
728            entry.tag = left.tag[i];
729            entry.metered = left.metered[i];
730            entry.roaming = left.roaming[i];
731            entry.defaultNetwork = left.defaultNetwork[i];
732            entry.rxBytes = left.rxBytes[i];
733            entry.rxPackets = left.rxPackets[i];
734            entry.txBytes = left.txBytes[i];
735            entry.txPackets = left.txPackets[i];
736            entry.operations = left.operations[i];
737
738            // find remote row that matches, and subtract
739            final int j = right.findIndexHinted(entry.iface, entry.uid, entry.set, entry.tag,
740                    entry.metered, entry.roaming, entry.defaultNetwork, i);
741            if (j != -1) {
742                // Found matching row, subtract remote value.
743                entry.rxBytes -= right.rxBytes[j];
744                entry.rxPackets -= right.rxPackets[j];
745                entry.txBytes -= right.txBytes[j];
746                entry.txPackets -= right.txPackets[j];
747                entry.operations -= right.operations[j];
748            }
749
750            if (entry.isNegative()) {
751                if (observer != null) {
752                    observer.foundNonMonotonic(left, i, right, j, cookie);
753                }
754                entry.rxBytes = Math.max(entry.rxBytes, 0);
755                entry.rxPackets = Math.max(entry.rxPackets, 0);
756                entry.txBytes = Math.max(entry.txBytes, 0);
757                entry.txPackets = Math.max(entry.txPackets, 0);
758                entry.operations = Math.max(entry.operations, 0);
759            }
760
761            result.addValues(entry);
762        }
763
764        return result;
765    }
766
767    /**
768     * Calculate and apply adjustments to captured statistics for 464xlat traffic counted twice.
769     *
770     * <p>This mutates both base and stacked traffic stats, to account respectively for
771     * double-counted traffic and IPv4/IPv6 header size difference.
772     *
773     * <p>For 464xlat traffic, xt_qtaguid sees every IPv4 packet twice, once as a native IPv4
774     * packet on the stacked interface, and once as translated to an IPv6 packet on the
775     * base interface. For correct stats accounting on the base interface, every 464xlat
776     * packet needs to be subtracted from the root UID on the base interface both for tx
777     * and rx traffic (http://b/12249687, http:/b/33681750).
778     *
779     * <p>This method will behave fine if {@code stackedIfaces} is an non-synchronized but add-only
780     * {@code ConcurrentHashMap}
781     * @param baseTraffic Traffic on the base interfaces. Will be mutated.
782     * @param stackedTraffic Stats with traffic stacked on top of our ifaces. Will also be mutated.
783     * @param stackedIfaces Mapping ipv6if -> ipv4if interface where traffic is counted on both.
784     */
785    public static void apply464xlatAdjustments(NetworkStats baseTraffic,
786            NetworkStats stackedTraffic, Map<String, String> stackedIfaces) {
787        // Total 464xlat traffic to subtract from uid 0 on all base interfaces.
788        // stackedIfaces may grow afterwards, but NetworkStats will just be resized automatically.
789        final NetworkStats adjustments = new NetworkStats(0, stackedIfaces.size());
790
791        // For recycling
792        Entry entry = null;
793        Entry adjust = new NetworkStats.Entry(IFACE_ALL, 0, 0, 0, 0, 0, 0, 0L, 0L, 0L, 0L, 0L);
794
795        for (int i = 0; i < stackedTraffic.size; i++) {
796            entry = stackedTraffic.getValues(i, entry);
797            if (entry.iface == null || !entry.iface.startsWith(CLATD_INTERFACE_PREFIX)) {
798                continue;
799            }
800            final String baseIface = stackedIfaces.get(entry.iface);
801            if (baseIface == null) {
802                continue;
803            }
804            // Subtract any 464lat traffic seen for the root UID on the current base interface.
805            adjust.iface = baseIface;
806            adjust.rxBytes = -(entry.rxBytes + entry.rxPackets * IPV4V6_HEADER_DELTA);
807            adjust.txBytes = -(entry.txBytes + entry.txPackets * IPV4V6_HEADER_DELTA);
808            adjust.rxPackets = -entry.rxPackets;
809            adjust.txPackets = -entry.txPackets;
810            adjustments.combineValues(adjust);
811
812            // For 464xlat traffic, xt_qtaguid only counts the bytes of the native IPv4 packet sent
813            // on the stacked interface with prefix "v4-" and drops the IPv6 header size after
814            // unwrapping. To account correctly for on-the-wire traffic, add the 20 additional bytes
815            // difference for all packets (http://b/12249687, http:/b/33681750).
816            entry.rxBytes += entry.rxPackets * IPV4V6_HEADER_DELTA;
817            entry.txBytes += entry.txPackets * IPV4V6_HEADER_DELTA;
818            stackedTraffic.setValues(i, entry);
819        }
820
821        baseTraffic.combineAllValues(adjustments);
822    }
823
824    /**
825     * Calculate and apply adjustments to captured statistics for 464xlat traffic counted twice.
826     *
827     * <p>This mutates the object this method is called on. Equivalent to calling
828     * {@link #apply464xlatAdjustments(NetworkStats, NetworkStats, Map)} with {@code this} as
829     * base and stacked traffic.
830     * @param stackedIfaces Mapping ipv6if -> ipv4if interface where traffic is counted on both.
831     */
832    public void apply464xlatAdjustments(Map<String, String> stackedIfaces) {
833        apply464xlatAdjustments(this, this, stackedIfaces);
834    }
835
836    /**
837     * Return total statistics grouped by {@link #iface}; doesn't mutate the
838     * original structure.
839     */
840    public NetworkStats groupedByIface() {
841        final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
842
843        final Entry entry = new Entry();
844        entry.uid = UID_ALL;
845        entry.set = SET_ALL;
846        entry.tag = TAG_NONE;
847        entry.metered = METERED_ALL;
848        entry.roaming = ROAMING_ALL;
849        entry.defaultNetwork = DEFAULT_NETWORK_ALL;
850        entry.operations = 0L;
851
852        for (int i = 0; i < size; i++) {
853            // skip specific tags, since already counted in TAG_NONE
854            if (tag[i] != TAG_NONE) continue;
855
856            entry.iface = iface[i];
857            entry.rxBytes = rxBytes[i];
858            entry.rxPackets = rxPackets[i];
859            entry.txBytes = txBytes[i];
860            entry.txPackets = txPackets[i];
861            stats.combineValues(entry);
862        }
863
864        return stats;
865    }
866
867    /**
868     * Return total statistics grouped by {@link #uid}; doesn't mutate the
869     * original structure.
870     */
871    public NetworkStats groupedByUid() {
872        final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
873
874        final Entry entry = new Entry();
875        entry.iface = IFACE_ALL;
876        entry.set = SET_ALL;
877        entry.tag = TAG_NONE;
878        entry.metered = METERED_ALL;
879        entry.roaming = ROAMING_ALL;
880        entry.defaultNetwork = DEFAULT_NETWORK_ALL;
881
882        for (int i = 0; i < size; i++) {
883            // skip specific tags, since already counted in TAG_NONE
884            if (tag[i] != TAG_NONE) continue;
885
886            entry.uid = uid[i];
887            entry.rxBytes = rxBytes[i];
888            entry.rxPackets = rxPackets[i];
889            entry.txBytes = txBytes[i];
890            entry.txPackets = txPackets[i];
891            entry.operations = operations[i];
892            stats.combineValues(entry);
893        }
894
895        return stats;
896    }
897
898    /**
899     * Return all rows except those attributed to the requested UID; doesn't
900     * mutate the original structure.
901     */
902    public NetworkStats withoutUids(int[] uids) {
903        final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
904
905        Entry entry = new Entry();
906        for (int i = 0; i < size; i++) {
907            entry = getValues(i, entry);
908            if (!ArrayUtils.contains(uids, entry.uid)) {
909                stats.addValues(entry);
910            }
911        }
912
913        return stats;
914    }
915
916    /**
917     * Only keep entries that match all specified filters.
918     *
919     * <p>This mutates the original structure in place. After this method is called,
920     * size is the number of matching entries, and capacity is the previous capacity.
921     * @param limitUid UID to filter for, or {@link #UID_ALL}.
922     * @param limitIfaces Interfaces to filter for, or {@link #INTERFACES_ALL}.
923     * @param limitTag Tag to filter for, or {@link #TAG_ALL}.
924     */
925    public void filter(int limitUid, String[] limitIfaces, int limitTag) {
926        if (limitUid == UID_ALL && limitTag == TAG_ALL && limitIfaces == INTERFACES_ALL) {
927            return;
928        }
929
930        Entry entry = new Entry();
931        int nextOutputEntry = 0;
932        for (int i = 0; i < size; i++) {
933            entry = getValues(i, entry);
934            final boolean matches =
935                    (limitUid == UID_ALL || limitUid == entry.uid)
936                    && (limitTag == TAG_ALL || limitTag == entry.tag)
937                    && (limitIfaces == INTERFACES_ALL
938                            || ArrayUtils.contains(limitIfaces, entry.iface));
939
940            if (matches) {
941                setValues(nextOutputEntry, entry);
942                nextOutputEntry++;
943            }
944        }
945
946        size = nextOutputEntry;
947    }
948
949    public void dump(String prefix, PrintWriter pw) {
950        pw.print(prefix);
951        pw.print("NetworkStats: elapsedRealtime="); pw.println(elapsedRealtime);
952        for (int i = 0; i < size; i++) {
953            pw.print(prefix);
954            pw.print("  ["); pw.print(i); pw.print("]");
955            pw.print(" iface="); pw.print(iface[i]);
956            pw.print(" uid="); pw.print(uid[i]);
957            pw.print(" set="); pw.print(setToString(set[i]));
958            pw.print(" tag="); pw.print(tagToString(tag[i]));
959            pw.print(" metered="); pw.print(meteredToString(metered[i]));
960            pw.print(" roaming="); pw.print(roamingToString(roaming[i]));
961            pw.print(" defaultNetwork="); pw.print(defaultNetworkToString(defaultNetwork[i]));
962            pw.print(" rxBytes="); pw.print(rxBytes[i]);
963            pw.print(" rxPackets="); pw.print(rxPackets[i]);
964            pw.print(" txBytes="); pw.print(txBytes[i]);
965            pw.print(" txPackets="); pw.print(txPackets[i]);
966            pw.print(" operations="); pw.println(operations[i]);
967        }
968    }
969
970    /**
971     * Return text description of {@link #set} value.
972     */
973    public static String setToString(int set) {
974        switch (set) {
975            case SET_ALL:
976                return "ALL";
977            case SET_DEFAULT:
978                return "DEFAULT";
979            case SET_FOREGROUND:
980                return "FOREGROUND";
981            case SET_DBG_VPN_IN:
982                return "DBG_VPN_IN";
983            case SET_DBG_VPN_OUT:
984                return "DBG_VPN_OUT";
985            default:
986                return "UNKNOWN";
987        }
988    }
989
990    /**
991     * Return text description of {@link #set} value.
992     */
993    public static String setToCheckinString(int set) {
994        switch (set) {
995            case SET_ALL:
996                return "all";
997            case SET_DEFAULT:
998                return "def";
999            case SET_FOREGROUND:
1000                return "fg";
1001            case SET_DBG_VPN_IN:
1002                return "vpnin";
1003            case SET_DBG_VPN_OUT:
1004                return "vpnout";
1005            default:
1006                return "unk";
1007        }
1008    }
1009
1010    /**
1011     * @return true if the querySet matches the dataSet.
1012     */
1013    public static boolean setMatches(int querySet, int dataSet) {
1014        if (querySet == dataSet) {
1015            return true;
1016        }
1017        // SET_ALL matches all non-debugging sets.
1018        return querySet == SET_ALL && dataSet < SET_DEBUG_START;
1019    }
1020
1021    /**
1022     * Return text description of {@link #tag} value.
1023     */
1024    public static String tagToString(int tag) {
1025        return "0x" + Integer.toHexString(tag);
1026    }
1027
1028    /**
1029     * Return text description of {@link #metered} value.
1030     */
1031    public static String meteredToString(int metered) {
1032        switch (metered) {
1033            case METERED_ALL:
1034                return "ALL";
1035            case METERED_NO:
1036                return "NO";
1037            case METERED_YES:
1038                return "YES";
1039            default:
1040                return "UNKNOWN";
1041        }
1042    }
1043
1044    /**
1045     * Return text description of {@link #roaming} value.
1046     */
1047    public static String roamingToString(int roaming) {
1048        switch (roaming) {
1049            case ROAMING_ALL:
1050                return "ALL";
1051            case ROAMING_NO:
1052                return "NO";
1053            case ROAMING_YES:
1054                return "YES";
1055            default:
1056                return "UNKNOWN";
1057        }
1058    }
1059
1060    /**
1061     * Return text description of {@link #defaultNetwork} value.
1062     */
1063    public static String defaultNetworkToString(int defaultNetwork) {
1064        switch (defaultNetwork) {
1065            case DEFAULT_NETWORK_ALL:
1066                return "ALL";
1067            case DEFAULT_NETWORK_NO:
1068                return "NO";
1069            case DEFAULT_NETWORK_YES:
1070                return "YES";
1071            default:
1072                return "UNKNOWN";
1073        }
1074    }
1075
1076    @Override
1077    public String toString() {
1078        final CharArrayWriter writer = new CharArrayWriter();
1079        dump("", new PrintWriter(writer));
1080        return writer.toString();
1081    }
1082
1083    @Override
1084    public int describeContents() {
1085        return 0;
1086    }
1087
1088    public static final Creator<NetworkStats> CREATOR = new Creator<NetworkStats>() {
1089        @Override
1090        public NetworkStats createFromParcel(Parcel in) {
1091            return new NetworkStats(in);
1092        }
1093
1094        @Override
1095        public NetworkStats[] newArray(int size) {
1096            return new NetworkStats[size];
1097        }
1098    };
1099
1100    public interface NonMonotonicObserver<C> {
1101        public void foundNonMonotonic(
1102                NetworkStats left, int leftIndex, NetworkStats right, int rightIndex, C cookie);
1103    }
1104
1105    /**
1106     * VPN accounting. Move some VPN's underlying traffic to other UIDs that use tun0 iface.
1107     *
1108     * This method should only be called on delta NetworkStats. Do not call this method on a
1109     * snapshot {@link NetworkStats} object because the tunUid and/or the underlyingIface may
1110     * change over time.
1111     *
1112     * This method performs adjustments for one active VPN package and one VPN iface at a time.
1113     *
1114     * It is possible for the VPN software to use multiple underlying networks. This method
1115     * only migrates traffic for the primary underlying network.
1116     *
1117     * @param tunUid uid of the VPN application
1118     * @param tunIface iface of the vpn tunnel
1119     * @param underlyingIface the primary underlying network iface used by the VPN application
1120     * @return true if it successfully adjusts the accounting for VPN, false otherwise
1121     */
1122    public boolean migrateTun(int tunUid, String tunIface, String underlyingIface) {
1123        Entry tunIfaceTotal = new Entry();
1124        Entry underlyingIfaceTotal = new Entry();
1125
1126        tunAdjustmentInit(tunUid, tunIface, underlyingIface, tunIfaceTotal, underlyingIfaceTotal);
1127
1128        // If tunIface < underlyingIface, it leaves the overhead traffic in the VPN app.
1129        // If tunIface > underlyingIface, the VPN app doesn't get credit for data compression.
1130        // Negative stats should be avoided.
1131        Entry pool = tunGetPool(tunIfaceTotal, underlyingIfaceTotal);
1132        if (pool.isEmpty()) {
1133            return true;
1134        }
1135        Entry moved =
1136                addTrafficToApplications(tunUid, tunIface, underlyingIface, tunIfaceTotal, pool);
1137        deductTrafficFromVpnApp(tunUid, underlyingIface, moved);
1138
1139        if (!moved.isEmpty()) {
1140            Slog.wtf(TAG, "Failed to deduct underlying network traffic from VPN package. Moved="
1141                    + moved);
1142            return false;
1143        }
1144        return true;
1145    }
1146
1147    /**
1148     * Initializes the data used by the migrateTun() method.
1149     *
1150     * This is the first pass iteration which does the following work:
1151     * (1) Adds up all the traffic through the tunUid's underlyingIface
1152     *     (both foreground and background).
1153     * (2) Adds up all the traffic through tun0 excluding traffic from the vpn app itself.
1154     */
1155    private void tunAdjustmentInit(int tunUid, String tunIface, String underlyingIface,
1156            Entry tunIfaceTotal, Entry underlyingIfaceTotal) {
1157        Entry recycle = new Entry();
1158        for (int i = 0; i < size; i++) {
1159            getValues(i, recycle);
1160            if (recycle.uid == UID_ALL) {
1161                throw new IllegalStateException(
1162                        "Cannot adjust VPN accounting on an iface aggregated NetworkStats.");
1163            } if (recycle.set == SET_DBG_VPN_IN || recycle.set == SET_DBG_VPN_OUT) {
1164                throw new IllegalStateException(
1165                        "Cannot adjust VPN accounting on a NetworkStats containing SET_DBG_VPN_*");
1166            }
1167
1168            if (recycle.uid == tunUid && recycle.tag == TAG_NONE
1169                    && Objects.equals(underlyingIface, recycle.iface)) {
1170                underlyingIfaceTotal.add(recycle);
1171            }
1172
1173            if (recycle.uid != tunUid && recycle.tag == TAG_NONE
1174                    && Objects.equals(tunIface, recycle.iface)) {
1175                // Add up all tunIface traffic excluding traffic from the vpn app itself.
1176                tunIfaceTotal.add(recycle);
1177            }
1178        }
1179    }
1180
1181    private static Entry tunGetPool(Entry tunIfaceTotal, Entry underlyingIfaceTotal) {
1182        Entry pool = new Entry();
1183        pool.rxBytes = Math.min(tunIfaceTotal.rxBytes, underlyingIfaceTotal.rxBytes);
1184        pool.rxPackets = Math.min(tunIfaceTotal.rxPackets, underlyingIfaceTotal.rxPackets);
1185        pool.txBytes = Math.min(tunIfaceTotal.txBytes, underlyingIfaceTotal.txBytes);
1186        pool.txPackets = Math.min(tunIfaceTotal.txPackets, underlyingIfaceTotal.txPackets);
1187        pool.operations = Math.min(tunIfaceTotal.operations, underlyingIfaceTotal.operations);
1188        return pool;
1189    }
1190
1191    private Entry addTrafficToApplications(int tunUid, String tunIface, String underlyingIface,
1192            Entry tunIfaceTotal, Entry pool) {
1193        Entry moved = new Entry();
1194        Entry tmpEntry = new Entry();
1195        tmpEntry.iface = underlyingIface;
1196        for (int i = 0; i < size; i++) {
1197            // the vpn app is excluded from the redistribution but all moved traffic will be
1198            // deducted from the vpn app (see deductTrafficFromVpnApp below).
1199            if (Objects.equals(iface[i], tunIface) && uid[i] != tunUid) {
1200                if (tunIfaceTotal.rxBytes > 0) {
1201                    tmpEntry.rxBytes = pool.rxBytes * rxBytes[i] / tunIfaceTotal.rxBytes;
1202                } else {
1203                    tmpEntry.rxBytes = 0;
1204                }
1205                if (tunIfaceTotal.rxPackets > 0) {
1206                    tmpEntry.rxPackets = pool.rxPackets * rxPackets[i] / tunIfaceTotal.rxPackets;
1207                } else {
1208                    tmpEntry.rxPackets = 0;
1209                }
1210                if (tunIfaceTotal.txBytes > 0) {
1211                    tmpEntry.txBytes = pool.txBytes * txBytes[i] / tunIfaceTotal.txBytes;
1212                } else {
1213                    tmpEntry.txBytes = 0;
1214                }
1215                if (tunIfaceTotal.txPackets > 0) {
1216                    tmpEntry.txPackets = pool.txPackets * txPackets[i] / tunIfaceTotal.txPackets;
1217                } else {
1218                    tmpEntry.txPackets = 0;
1219                }
1220                if (tunIfaceTotal.operations > 0) {
1221                    tmpEntry.operations =
1222                            pool.operations * operations[i] / tunIfaceTotal.operations;
1223                } else {
1224                    tmpEntry.operations = 0;
1225                }
1226                tmpEntry.uid = uid[i];
1227                tmpEntry.tag = tag[i];
1228                tmpEntry.set = set[i];
1229                tmpEntry.metered = metered[i];
1230                tmpEntry.roaming = roaming[i];
1231                tmpEntry.defaultNetwork = defaultNetwork[i];
1232                combineValues(tmpEntry);
1233                if (tag[i] == TAG_NONE) {
1234                    moved.add(tmpEntry);
1235                    // Add debug info
1236                    tmpEntry.set = SET_DBG_VPN_IN;
1237                    combineValues(tmpEntry);
1238                }
1239            }
1240        }
1241        return moved;
1242    }
1243
1244    private void deductTrafficFromVpnApp(int tunUid, String underlyingIface, Entry moved) {
1245        // Add debug info
1246        moved.uid = tunUid;
1247        moved.set = SET_DBG_VPN_OUT;
1248        moved.tag = TAG_NONE;
1249        moved.iface = underlyingIface;
1250        moved.metered = METERED_ALL;
1251        moved.roaming = ROAMING_ALL;
1252        moved.defaultNetwork = DEFAULT_NETWORK_ALL;
1253        combineValues(moved);
1254
1255        // Caveat: if the vpn software uses tag, the total tagged traffic may be greater than
1256        // the TAG_NONE traffic.
1257        //
1258        // Relies on the fact that the underlying traffic only has state ROAMING_NO and METERED_NO,
1259        // which should be the case as it comes directly from the /proc file. We only blend in the
1260        // roaming data after applying these adjustments, by checking the NetworkIdentity of the
1261        // underlying iface.
1262        int idxVpnBackground = findIndex(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE,
1263                METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO);
1264        if (idxVpnBackground != -1) {
1265            tunSubtract(idxVpnBackground, this, moved);
1266        }
1267
1268        int idxVpnForeground = findIndex(underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE,
1269                METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO);
1270        if (idxVpnForeground != -1) {
1271            tunSubtract(idxVpnForeground, this, moved);
1272        }
1273    }
1274
1275    private static void tunSubtract(int i, NetworkStats left, Entry right) {
1276        long rxBytes = Math.min(left.rxBytes[i], right.rxBytes);
1277        left.rxBytes[i] -= rxBytes;
1278        right.rxBytes -= rxBytes;
1279
1280        long rxPackets = Math.min(left.rxPackets[i], right.rxPackets);
1281        left.rxPackets[i] -= rxPackets;
1282        right.rxPackets -= rxPackets;
1283
1284        long txBytes = Math.min(left.txBytes[i], right.txBytes);
1285        left.txBytes[i] -= txBytes;
1286        right.txBytes -= txBytes;
1287
1288        long txPackets = Math.min(left.txPackets[i], right.txPackets);
1289        left.txPackets[i] -= txPackets;
1290        right.txPackets -= txPackets;
1291    }
1292}
1293