2 * Copyright (C) 2011 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
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;
25 import com.android.internal.annotations.VisibleForTesting;
26 import com.android.internal.util.ArrayUtils;
28 import libcore.util.EmptyArray;
30 import java.io.CharArrayWriter;
31 import java.io.PrintWriter;
32 import java.util.Arrays;
33 import java.util.HashSet;
35 import java.util.Objects;
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.
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;
68 /** Include all interfaces when filtering */
69 public static final String[] INTERFACES_ALL = null;
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;
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;
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;
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;
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;
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;
106 // TODO: move fields to "mVariable" notation
109 * {@link SystemClock#elapsedRealtime()} timestamp when this data was
112 private long elapsedRealtime;
114 private int capacity;
115 private String[] iface;
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;
128 public static class Entry {
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
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
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
150 public int defaultNetwork;
152 public long rxPackets;
154 public long txPackets;
155 public long operations;
158 this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
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,
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);
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,
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;
189 public boolean isNegative() {
190 return rxBytes < 0 || rxPackets < 0 || txBytes < 0 || txPackets < 0 || operations < 0;
193 public boolean isEmpty() {
194 return rxBytes == 0 && rxPackets == 0 && txBytes == 0 && txPackets == 0
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;
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();
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);
238 public int hashCode() {
239 return Objects.hash(uid, set, tag, metered, roaming, defaultNetwork, iface);
243 public NetworkStats(long elapsedRealtime, int initialSize) {
244 this.elapsedRealtime = elapsedRealtime;
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];
261 // Special case for use by NetworkStatsFactory to start out *really* empty.
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();
285 public void writeToParcel(Parcel dest, int flags) {
286 dest.writeLong(elapsedRealtime);
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);
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);
315 * Clear all data stored in this object.
317 public void clear() {
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;
334 public NetworkStats addIfaceValues(
335 String iface, long rxBytes, long rxPackets, long txBytes, long txPackets) {
337 iface, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets, 0L);
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));
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,
351 return addValues(new Entry(
352 iface, uid, set, tag, metered, roaming, defaultNetwork, rxBytes, rxPackets,
353 txBytes, txPackets, operations));
357 * Add new stats entry, copying from given {@link Entry}. The {@link Entry}
358 * object can be recycled across multiple calls.
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;
378 setValues(size, entry);
384 private void setValues(int i, Entry entry) {
385 iface[i] = entry.iface;
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;
400 * Return specific stats entry.
402 public Entry getValues(int i, Entry recycle) {
403 final Entry entry = recycle != null ? recycle : new Entry();
404 entry.iface = iface[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];
419 public long getElapsedRealtime() {
420 return elapsedRealtime;
423 public void setElapsedRealtime(long time) {
424 elapsedRealtime = time;
428 * Return age of this {@link NetworkStats} object with respect to
429 * {@link SystemClock#elapsedRealtime()}.
431 public long getElapsedRealtimeAge() {
432 return SystemClock.elapsedRealtime() - elapsedRealtime;
440 public int internalSize() {
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);
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));
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.
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);
467 // only create new entry when positive contribution
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;
480 * Combine all values from another {@link NetworkStats} into this object.
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);
491 * Find first stats index that matches the requested parameters.
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])) {
507 * Find first stats index that matches the requested parameters, starting
508 * search around the hinted index as an optimization.
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;
516 // search outwards from hint index, alternating forward and backward
518 if (offset % 2 == 0) {
519 i = (hintIndex + halfOffset) % size;
521 i = (size + hintIndex - halfOffset - 1) % size;
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])) {
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.
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],
546 operations[i] = stats.operations[j];
552 * Return list of unique interfaces known by this data structure.
554 public String[] getUniqueIfaces() {
555 final HashSet<String> ifaces = new HashSet<String>();
556 for (String iface : this.iface) {
557 if (iface != IFACE_ALL) {
561 return ifaces.toArray(new String[ifaces.size()]);
565 * Return list of unique UIDs known by this data structure.
567 public int[] getUniqueUids() {
568 final SparseBooleanArray uids = new SparseBooleanArray();
569 for (int uid : this.uid) {
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);
582 * Return total bytes represented by this snapshot object, usually used when
583 * checking if a {@link #subtract(NetworkStats)} delta passes a threshold.
585 public long getTotalBytes() {
586 final Entry entry = getTotal(null);
587 return entry.rxBytes + entry.txBytes;
591 * Return total of all fields represented by this snapshot object.
593 public Entry getTotal(Entry recycle) {
594 return getTotal(recycle, null, UID_ALL, false);
598 * Return total of all fields represented by this snapshot object matching
599 * the requested {@link #uid}.
601 public Entry getTotal(Entry recycle, int limitUid) {
602 return getTotal(recycle, null, limitUid, false);
606 * Return total of all fields represented by this snapshot object matching
607 * the requested {@link #iface}.
609 public Entry getTotal(Entry recycle, HashSet<String> limitIface) {
610 return getTotal(recycle, limitIface, UID_ALL, false);
613 public Entry getTotalIncludingTags(Entry recycle) {
614 return getTotal(recycle, null, UID_ALL, true);
618 * Return total of all fields represented by this snapshot object matching
619 * the requested {@link #iface} and {@link #uid}.
621 * @param limitIface Set of {@link #iface} to include in total; or {@code
622 * null} to include all ifaces.
624 private Entry getTotal(
625 Entry recycle, HashSet<String> limitIface, int limitUid, boolean includeTags) {
626 final Entry entry = recycle != null ? recycle : new Entry();
628 entry.iface = IFACE_ALL;
629 entry.uid = limitUid;
631 entry.tag = TAG_NONE;
632 entry.metered = METERED_ALL;
633 entry.roaming = ROAMING_ALL;
634 entry.defaultNetwork = DEFAULT_NETWORK_ALL;
639 entry.operations = 0;
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]));
645 if (matchesUid && matchesIface) {
646 // skip specific tags, since already counted in TAG_NONE
647 if (tag[i] != TAG_NONE && !includeTags) continue;
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];
660 * Fast path for battery stats.
662 public long getTotalPackets() {
664 for (int i = size-1; i >= 0; i--) {
665 total += rxPackets[i] + txPackets[i];
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.
675 public NetworkStats subtract(NetworkStats right) {
676 return subtract(this, right, null, null);
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.
684 * If counters have rolled backwards, they are clamped to {@code 0} and
685 * reported to the given {@link NonMonotonicObserver}.
687 public static <C> NetworkStats subtract(NetworkStats left, NetworkStats right,
688 NonMonotonicObserver<C> observer, C cookie) {
689 return subtract(left, right, observer, cookie, null);
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.
697 * If counters have rolled backwards, they are clamped to {@code 0} and
698 * reported to the given {@link NonMonotonicObserver}.
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
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);
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) {
720 result.elapsedRealtime = deltaRealtime;
722 result = new NetworkStats(deltaRealtime, left.size);
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];
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);
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];
750 if (entry.isNegative()) {
751 if (observer != null) {
752 observer.foundNonMonotonic(left, i, right, j, cookie);
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);
761 result.addValues(entry);
768 * Calculate and apply adjustments to captured statistics for 464xlat traffic counted twice.
770 * <p>This mutates both base and stacked traffic stats, to account respectively for
771 * double-counted traffic and IPv4/IPv6 header size difference.
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).
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.
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());
793 Entry adjust = new NetworkStats.Entry(IFACE_ALL, 0, 0, 0, 0, 0, 0, 0L, 0L, 0L, 0L, 0L);
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)) {
800 final String baseIface = stackedIfaces.get(entry.iface);
801 if (baseIface == null) {
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);
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);
821 baseTraffic.combineAllValues(adjustments);
825 * Calculate and apply adjustments to captured statistics for 464xlat traffic counted twice.
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.
832 public void apply464xlatAdjustments(Map<String, String> stackedIfaces) {
833 apply464xlatAdjustments(this, this, stackedIfaces);
837 * Return total statistics grouped by {@link #iface}; doesn't mutate the
838 * original structure.
840 public NetworkStats groupedByIface() {
841 final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
843 final Entry entry = new Entry();
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;
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;
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);
868 * Return total statistics grouped by {@link #uid}; doesn't mutate the
869 * original structure.
871 public NetworkStats groupedByUid() {
872 final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
874 final Entry entry = new Entry();
875 entry.iface = IFACE_ALL;
877 entry.tag = TAG_NONE;
878 entry.metered = METERED_ALL;
879 entry.roaming = ROAMING_ALL;
880 entry.defaultNetwork = DEFAULT_NETWORK_ALL;
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;
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);
899 * Return all rows except those attributed to the requested UID; doesn't
900 * mutate the original structure.
902 public NetworkStats withoutUids(int[] uids) {
903 final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
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);
917 * Only keep entries that match all specified filters.
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}.
925 public void filter(int limitUid, String[] limitIfaces, int limitTag) {
926 if (limitUid == UID_ALL && limitTag == TAG_ALL && limitIfaces == INTERFACES_ALL) {
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));
941 setValues(nextOutputEntry, entry);
946 size = nextOutputEntry;
949 public void dump(String prefix, PrintWriter pw) {
951 pw.print("NetworkStats: elapsedRealtime="); pw.println(elapsedRealtime);
952 for (int i = 0; i < size; i++) {
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]);
971 * Return text description of {@link #set} value.
973 public static String setToString(int set) {
983 case SET_DBG_VPN_OUT:
984 return "DBG_VPN_OUT";
991 * Return text description of {@link #set} value.
993 public static String setToCheckinString(int set) {
1001 case SET_DBG_VPN_IN:
1003 case SET_DBG_VPN_OUT:
1011 * @return true if the querySet matches the dataSet.
1013 public static boolean setMatches(int querySet, int dataSet) {
1014 if (querySet == dataSet) {
1017 // SET_ALL matches all non-debugging sets.
1018 return querySet == SET_ALL && dataSet < SET_DEBUG_START;
1022 * Return text description of {@link #tag} value.
1024 public static String tagToString(int tag) {
1025 return "0x" + Integer.toHexString(tag);
1029 * Return text description of {@link #metered} value.
1031 public static String meteredToString(int metered) {
1045 * Return text description of {@link #roaming} value.
1047 public static String roamingToString(int roaming) {
1061 * Return text description of {@link #defaultNetwork} value.
1063 public static String defaultNetworkToString(int defaultNetwork) {
1064 switch (defaultNetwork) {
1065 case DEFAULT_NETWORK_ALL:
1067 case DEFAULT_NETWORK_NO:
1069 case DEFAULT_NETWORK_YES:
1077 public String toString() {
1078 final CharArrayWriter writer = new CharArrayWriter();
1079 dump("", new PrintWriter(writer));
1080 return writer.toString();
1084 public int describeContents() {
1088 public static final Creator<NetworkStats> CREATOR = new Creator<NetworkStats>() {
1090 public NetworkStats createFromParcel(Parcel in) {
1091 return new NetworkStats(in);
1095 public NetworkStats[] newArray(int size) {
1096 return new NetworkStats[size];
1100 public interface NonMonotonicObserver<C> {
1101 public void foundNonMonotonic(
1102 NetworkStats left, int leftIndex, NetworkStats right, int rightIndex, C cookie);
1106 * VPN accounting. Move some VPN's underlying traffic to other UIDs that use tun0 iface.
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
1112 * This method performs adjustments for one active VPN package and one VPN iface at a time.
1114 * It is possible for the VPN software to use multiple underlying networks. This method
1115 * only migrates traffic for the primary underlying network.
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
1122 public boolean migrateTun(int tunUid, String tunIface, String underlyingIface) {
1123 Entry tunIfaceTotal = new Entry();
1124 Entry underlyingIfaceTotal = new Entry();
1126 tunAdjustmentInit(tunUid, tunIface, underlyingIface, tunIfaceTotal, underlyingIfaceTotal);
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()) {
1136 addTrafficToApplications(tunUid, tunIface, underlyingIface, tunIfaceTotal, pool);
1137 deductTrafficFromVpnApp(tunUid, underlyingIface, moved);
1139 if (!moved.isEmpty()) {
1140 Slog.wtf(TAG, "Failed to deduct underlying network traffic from VPN package. Moved="
1148 * Initializes the data used by the migrateTun() method.
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.
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_*");
1168 if (recycle.uid == tunUid && recycle.tag == TAG_NONE
1169 && Objects.equals(underlyingIface, recycle.iface)) {
1170 underlyingIfaceTotal.add(recycle);
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);
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);
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;
1203 tmpEntry.rxBytes = 0;
1205 if (tunIfaceTotal.rxPackets > 0) {
1206 tmpEntry.rxPackets = pool.rxPackets * rxPackets[i] / tunIfaceTotal.rxPackets;
1208 tmpEntry.rxPackets = 0;
1210 if (tunIfaceTotal.txBytes > 0) {
1211 tmpEntry.txBytes = pool.txBytes * txBytes[i] / tunIfaceTotal.txBytes;
1213 tmpEntry.txBytes = 0;
1215 if (tunIfaceTotal.txPackets > 0) {
1216 tmpEntry.txPackets = pool.txPackets * txPackets[i] / tunIfaceTotal.txPackets;
1218 tmpEntry.txPackets = 0;
1220 if (tunIfaceTotal.operations > 0) {
1221 tmpEntry.operations =
1222 pool.operations * operations[i] / tunIfaceTotal.operations;
1224 tmpEntry.operations = 0;
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);
1236 tmpEntry.set = SET_DBG_VPN_IN;
1237 combineValues(tmpEntry);
1244 private void deductTrafficFromVpnApp(int tunUid, String underlyingIface, Entry moved) {
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);
1255 // Caveat: if the vpn software uses tag, the total tagged traffic may be greater than
1256 // the TAG_NONE traffic.
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);
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);
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;
1280 long rxPackets = Math.min(left.rxPackets[i], right.rxPackets);
1281 left.rxPackets[i] -= rxPackets;
1282 right.rxPackets -= rxPackets;
1284 long txBytes = Math.min(left.txBytes[i], right.txBytes);
1285 left.txBytes[i] -= txBytes;
1286 right.txBytes -= txBytes;
1288 long txPackets = Math.min(left.txPackets[i], right.txPackets);
1289 left.txPackets[i] -= txPackets;
1290 right.txPackets -= txPackets;