OSDN Git Service

f0dd2629f93c459f679d3d3c6d7b856000b83bc1
[android-x86/frameworks-base.git] / core / java / android / net / NetworkStats.java
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package android.net;
18
19 import android.os.Parcel;
20 import android.os.Parcelable;
21 import android.os.SystemClock;
22 import android.util.Slog;
23 import android.util.SparseBooleanArray;
24
25 import com.android.internal.annotations.VisibleForTesting;
26 import com.android.internal.util.ArrayUtils;
27
28 import libcore.util.EmptyArray;
29
30 import java.io.CharArrayWriter;
31 import java.io.PrintWriter;
32 import java.util.Arrays;
33 import java.util.HashSet;
34 import java.util.Map;
35 import 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  */
46 public 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 }