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