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