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.Log;
     23 import android.util.SparseBooleanArray;
     24 
     25 import com.android.internal.util.Objects;
     26 
     27 import java.io.CharArrayWriter;
     28 import java.io.PrintWriter;
     29 import java.util.Arrays;
     30 import java.util.HashSet;
     31 
     32 /**
     33  * Collection of active network statistics. Can contain summary details across
     34  * all interfaces, or details with per-UID granularity. Internally stores data
     35  * as a large table, closely matching {@code /proc/} data format. This structure
     36  * optimizes for rapid in-memory comparison, but consider using
     37  * {@link NetworkStatsHistory} when persisting.
     38  *
     39  * @hide
     40  */
     41 public class NetworkStats implements Parcelable {
     42     private static final String TAG = "NetworkStats";
     43 
     44     /** {@link #iface} value when interface details unavailable. */
     45     public static final String IFACE_ALL = null;
     46     /** {@link #uid} value when UID details unavailable. */
     47     public static final int UID_ALL = -1;
     48     /** {@link #set} value when all sets combined. */
     49     public static final int SET_ALL = -1;
     50     /** {@link #set} value where background data is accounted. */
     51     public static final int SET_DEFAULT = 0;
     52     /** {@link #set} value where foreground data is accounted. */
     53     public static final int SET_FOREGROUND = 1;
     54     /** {@link #tag} value for total data across all tags. */
     55     public static final int TAG_NONE = 0;
     56 
     57     /**
     58      * {@link SystemClock#elapsedRealtime()} timestamp when this data was
     59      * generated.
     60      */
     61     private final long elapsedRealtime;
     62     private int size;
     63     private String[] iface;
     64     private int[] uid;
     65     private int[] set;
     66     private int[] tag;
     67     private long[] rxBytes;
     68     private long[] rxPackets;
     69     private long[] txBytes;
     70     private long[] txPackets;
     71     private long[] operations;
     72 
     73     public static class Entry {
     74         public String iface;
     75         public int uid;
     76         public int set;
     77         public int tag;
     78         public long rxBytes;
     79         public long rxPackets;
     80         public long txBytes;
     81         public long txPackets;
     82         public long operations;
     83 
     84         public Entry() {
     85             this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
     86         }
     87 
     88         public Entry(long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) {
     89             this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets,
     90                     operations);
     91         }
     92 
     93         public Entry(String iface, int uid, int set, int tag, long rxBytes, long rxPackets,
     94                 long txBytes, long txPackets, long operations) {
     95             this.iface = iface;
     96             this.uid = uid;
     97             this.set = set;
     98             this.tag = tag;
     99             this.rxBytes = rxBytes;
    100             this.rxPackets = rxPackets;
    101             this.txBytes = txBytes;
    102             this.txPackets = txPackets;
    103             this.operations = operations;
    104         }
    105 
    106         @Override
    107         public String toString() {
    108             final StringBuilder builder = new StringBuilder();
    109             builder.append("iface=").append(iface);
    110             builder.append(" uid=").append(uid);
    111             builder.append(" set=").append(setToString(set));
    112             builder.append(" tag=").append(tagToString(tag));
    113             builder.append(" rxBytes=").append(rxBytes);
    114             builder.append(" rxPackets=").append(rxPackets);
    115             builder.append(" txBytes=").append(txBytes);
    116             builder.append(" txPackets=").append(txPackets);
    117             builder.append(" operations=").append(operations);
    118             return builder.toString();
    119         }
    120     }
    121 
    122     public NetworkStats(long elapsedRealtime, int initialSize) {
    123         this.elapsedRealtime = elapsedRealtime;
    124         this.size = 0;
    125         this.iface = new String[initialSize];
    126         this.uid = new int[initialSize];
    127         this.set = new int[initialSize];
    128         this.tag = new int[initialSize];
    129         this.rxBytes = new long[initialSize];
    130         this.rxPackets = new long[initialSize];
    131         this.txBytes = new long[initialSize];
    132         this.txPackets = new long[initialSize];
    133         this.operations = new long[initialSize];
    134     }
    135 
    136     public NetworkStats(Parcel parcel) {
    137         elapsedRealtime = parcel.readLong();
    138         size = parcel.readInt();
    139         iface = parcel.createStringArray();
    140         uid = parcel.createIntArray();
    141         set = parcel.createIntArray();
    142         tag = parcel.createIntArray();
    143         rxBytes = parcel.createLongArray();
    144         rxPackets = parcel.createLongArray();
    145         txBytes = parcel.createLongArray();
    146         txPackets = parcel.createLongArray();
    147         operations = parcel.createLongArray();
    148     }
    149 
    150     /** {@inheritDoc} */
    151     public void writeToParcel(Parcel dest, int flags) {
    152         dest.writeLong(elapsedRealtime);
    153         dest.writeInt(size);
    154         dest.writeStringArray(iface);
    155         dest.writeIntArray(uid);
    156         dest.writeIntArray(set);
    157         dest.writeIntArray(tag);
    158         dest.writeLongArray(rxBytes);
    159         dest.writeLongArray(rxPackets);
    160         dest.writeLongArray(txBytes);
    161         dest.writeLongArray(txPackets);
    162         dest.writeLongArray(operations);
    163     }
    164 
    165     // @VisibleForTesting
    166     public NetworkStats addIfaceValues(
    167             String iface, long rxBytes, long rxPackets, long txBytes, long txPackets) {
    168         return addValues(
    169                 iface, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets, 0L);
    170     }
    171 
    172     // @VisibleForTesting
    173     public NetworkStats addValues(String iface, int uid, int set, int tag, long rxBytes,
    174             long rxPackets, long txBytes, long txPackets, long operations) {
    175         return addValues(new Entry(
    176                 iface, uid, set, tag, rxBytes, rxPackets, txBytes, txPackets, operations));
    177     }
    178 
    179     /**
    180      * Add new stats entry, copying from given {@link Entry}. The {@link Entry}
    181      * object can be recycled across multiple calls.
    182      */
    183     public NetworkStats addValues(Entry entry) {
    184         if (size >= this.iface.length) {
    185             final int newLength = Math.max(iface.length, 10) * 3 / 2;
    186             iface = Arrays.copyOf(iface, newLength);
    187             uid = Arrays.copyOf(uid, newLength);
    188             set = Arrays.copyOf(set, newLength);
    189             tag = Arrays.copyOf(tag, newLength);
    190             rxBytes = Arrays.copyOf(rxBytes, newLength);
    191             rxPackets = Arrays.copyOf(rxPackets, newLength);
    192             txBytes = Arrays.copyOf(txBytes, newLength);
    193             txPackets = Arrays.copyOf(txPackets, newLength);
    194             operations = Arrays.copyOf(operations, newLength);
    195         }
    196 
    197         iface[size] = entry.iface;
    198         uid[size] = entry.uid;
    199         set[size] = entry.set;
    200         tag[size] = entry.tag;
    201         rxBytes[size] = entry.rxBytes;
    202         rxPackets[size] = entry.rxPackets;
    203         txBytes[size] = entry.txBytes;
    204         txPackets[size] = entry.txPackets;
    205         operations[size] = entry.operations;
    206         size++;
    207 
    208         return this;
    209     }
    210 
    211     /**
    212      * Return specific stats entry.
    213      */
    214     public Entry getValues(int i, Entry recycle) {
    215         final Entry entry = recycle != null ? recycle : new Entry();
    216         entry.iface = iface[i];
    217         entry.uid = uid[i];
    218         entry.set = set[i];
    219         entry.tag = tag[i];
    220         entry.rxBytes = rxBytes[i];
    221         entry.rxPackets = rxPackets[i];
    222         entry.txBytes = txBytes[i];
    223         entry.txPackets = txPackets[i];
    224         entry.operations = operations[i];
    225         return entry;
    226     }
    227 
    228     public long getElapsedRealtime() {
    229         return elapsedRealtime;
    230     }
    231 
    232     /**
    233      * Return age of this {@link NetworkStats} object with respect to
    234      * {@link SystemClock#elapsedRealtime()}.
    235      */
    236     public long getElapsedRealtimeAge() {
    237         return SystemClock.elapsedRealtime() - elapsedRealtime;
    238     }
    239 
    240     public int size() {
    241         return size;
    242     }
    243 
    244     // @VisibleForTesting
    245     public int internalSize() {
    246         return iface.length;
    247     }
    248 
    249     @Deprecated
    250     public NetworkStats combineValues(String iface, int uid, int tag, long rxBytes, long rxPackets,
    251             long txBytes, long txPackets, long operations) {
    252         return combineValues(
    253                 iface, uid, SET_DEFAULT, tag, rxBytes, rxPackets, txBytes, txPackets, operations);
    254     }
    255 
    256     public NetworkStats combineValues(String iface, int uid, int set, int tag, long rxBytes,
    257             long rxPackets, long txBytes, long txPackets, long operations) {
    258         return combineValues(new Entry(
    259                 iface, uid, set, tag, rxBytes, rxPackets, txBytes, txPackets, operations));
    260     }
    261 
    262     /**
    263      * Combine given values with an existing row, or create a new row if
    264      * {@link #findIndex(String, int, int, int)} is unable to find match. Can
    265      * also be used to subtract values from existing rows.
    266      */
    267     public NetworkStats combineValues(Entry entry) {
    268         final int i = findIndex(entry.iface, entry.uid, entry.set, entry.tag);
    269         if (i == -1) {
    270             // only create new entry when positive contribution
    271             addValues(entry);
    272         } else {
    273             rxBytes[i] += entry.rxBytes;
    274             rxPackets[i] += entry.rxPackets;
    275             txBytes[i] += entry.txBytes;
    276             txPackets[i] += entry.txPackets;
    277             operations[i] += entry.operations;
    278         }
    279         return this;
    280     }
    281 
    282     /**
    283      * Combine all values from another {@link NetworkStats} into this object.
    284      */
    285     public void combineAllValues(NetworkStats another) {
    286         NetworkStats.Entry entry = null;
    287         for (int i = 0; i < another.size; i++) {
    288             entry = another.getValues(i, entry);
    289             combineValues(entry);
    290         }
    291     }
    292 
    293     /**
    294      * Find first stats index that matches the requested parameters.
    295      */
    296     public int findIndex(String iface, int uid, int set, int tag) {
    297         for (int i = 0; i < size; i++) {
    298             if (Objects.equal(iface, this.iface[i]) && uid == this.uid[i] && set == this.set[i]
    299                     && tag == this.tag[i]) {
    300                 return i;
    301             }
    302         }
    303         return -1;
    304     }
    305 
    306     /**
    307      * Splice in {@link #operations} from the given {@link NetworkStats} based
    308      * on matching {@link #uid} and {@link #tag} rows. Ignores {@link #iface},
    309      * since operation counts are at data layer.
    310      */
    311     public void spliceOperationsFrom(NetworkStats stats) {
    312         for (int i = 0; i < size; i++) {
    313             final int j = stats.findIndex(IFACE_ALL, uid[i], set[i], tag[i]);
    314             if (j == -1) {
    315                 operations[i] = 0;
    316             } else {
    317                 operations[i] = stats.operations[j];
    318             }
    319         }
    320     }
    321 
    322     /**
    323      * Return list of unique interfaces known by this data structure.
    324      */
    325     public String[] getUniqueIfaces() {
    326         final HashSet<String> ifaces = new HashSet<String>();
    327         for (String iface : this.iface) {
    328             if (iface != IFACE_ALL) {
    329                 ifaces.add(iface);
    330             }
    331         }
    332         return ifaces.toArray(new String[ifaces.size()]);
    333     }
    334 
    335     /**
    336      * Return list of unique UIDs known by this data structure.
    337      */
    338     public int[] getUniqueUids() {
    339         final SparseBooleanArray uids = new SparseBooleanArray();
    340         for (int uid : this.uid) {
    341             uids.put(uid, true);
    342         }
    343 
    344         final int size = uids.size();
    345         final int[] result = new int[size];
    346         for (int i = 0; i < size; i++) {
    347             result[i] = uids.keyAt(i);
    348         }
    349         return result;
    350     }
    351 
    352     /**
    353      * Return total bytes represented by this snapshot object, usually used when
    354      * checking if a {@link #subtract(NetworkStats)} delta passes a threshold.
    355      */
    356     public long getTotalBytes() {
    357         final Entry entry = getTotal(null);
    358         return entry.rxBytes + entry.txBytes;
    359     }
    360 
    361     /**
    362      * Return total of all fields represented by this snapshot object.
    363      */
    364     public Entry getTotal(Entry recycle) {
    365         return getTotal(recycle, null, UID_ALL);
    366     }
    367 
    368     /**
    369      * Return total of all fields represented by this snapshot object matching
    370      * the requested {@link #uid}.
    371      */
    372     public Entry getTotal(Entry recycle, int limitUid) {
    373         return getTotal(recycle, null, limitUid);
    374     }
    375 
    376     /**
    377      * Return total of all fields represented by this snapshot object matching
    378      * the requested {@link #iface}.
    379      */
    380     public Entry getTotal(Entry recycle, HashSet<String> limitIface) {
    381         return getTotal(recycle, limitIface, UID_ALL);
    382     }
    383 
    384     /**
    385      * Return total of all fields represented by this snapshot object matching
    386      * the requested {@link #iface} and {@link #uid}.
    387      *
    388      * @param limitIface Set of {@link #iface} to include in total; or {@code
    389      *            null} to include all ifaces.
    390      */
    391     private Entry getTotal(Entry recycle, HashSet<String> limitIface, int limitUid) {
    392         final Entry entry = recycle != null ? recycle : new Entry();
    393 
    394         entry.iface = IFACE_ALL;
    395         entry.uid = limitUid;
    396         entry.set = SET_ALL;
    397         entry.tag = TAG_NONE;
    398         entry.rxBytes = 0;
    399         entry.rxPackets = 0;
    400         entry.txBytes = 0;
    401         entry.txPackets = 0;
    402         entry.operations = 0;
    403 
    404         for (int i = 0; i < size; i++) {
    405             final boolean matchesUid = (limitUid == UID_ALL) || (limitUid == uid[i]);
    406             final boolean matchesIface = (limitIface == null) || (limitIface.contains(iface[i]));
    407 
    408             if (matchesUid && matchesIface) {
    409                 // skip specific tags, since already counted in TAG_NONE
    410                 if (tag[i] != TAG_NONE) continue;
    411 
    412                 entry.rxBytes += rxBytes[i];
    413                 entry.rxPackets += rxPackets[i];
    414                 entry.txBytes += txBytes[i];
    415                 entry.txPackets += txPackets[i];
    416                 entry.operations += operations[i];
    417             }
    418         }
    419         return entry;
    420     }
    421 
    422     /**
    423      * Subtract the given {@link NetworkStats}, effectively leaving the delta
    424      * between two snapshots in time. Assumes that statistics rows collect over
    425      * time, and that none of them have disappeared.
    426      *
    427      * @throws IllegalArgumentException when given {@link NetworkStats} is
    428      *             non-monotonic.
    429      */
    430     public NetworkStats subtract(NetworkStats value) {
    431         return subtract(value, true, false);
    432     }
    433 
    434     /**
    435      * Subtract the given {@link NetworkStats}, effectively leaving the delta
    436      * between two snapshots in time. Assumes that statistics rows collect over
    437      * time, and that none of them have disappeared.
    438      * <p>
    439      * Instead of throwing when counters are non-monotonic, this variant clamps
    440      * results to never be negative.
    441      */
    442     public NetworkStats subtractClamped(NetworkStats value) {
    443         return subtract(value, false, true);
    444     }
    445 
    446     /**
    447      * Subtract the given {@link NetworkStats}, effectively leaving the delta
    448      * between two snapshots in time. Assumes that statistics rows collect over
    449      * time, and that none of them have disappeared.
    450      *
    451      * @param enforceMonotonic Validate that incoming value is strictly
    452      *            monotonic compared to this object.
    453      * @param clampNegative Instead of throwing like {@code enforceMonotonic},
    454      *            clamp resulting counters at 0 to prevent negative values.
    455      */
    456     private NetworkStats subtract(
    457             NetworkStats value, boolean enforceMonotonic, boolean clampNegative) {
    458         final long deltaRealtime = this.elapsedRealtime - value.elapsedRealtime;
    459         if (enforceMonotonic && deltaRealtime < 0) {
    460             throw new IllegalArgumentException("found non-monotonic realtime");
    461         }
    462 
    463         // result will have our rows, and elapsed time between snapshots
    464         final Entry entry = new Entry();
    465         final NetworkStats result = new NetworkStats(deltaRealtime, size);
    466         for (int i = 0; i < size; i++) {
    467             entry.iface = iface[i];
    468             entry.uid = uid[i];
    469             entry.set = set[i];
    470             entry.tag = tag[i];
    471 
    472             // find remote row that matches, and subtract
    473             final int j = value.findIndex(entry.iface, entry.uid, entry.set, entry.tag);
    474             if (j == -1) {
    475                 // newly appearing row, return entire value
    476                 entry.rxBytes = rxBytes[i];
    477                 entry.rxPackets = rxPackets[i];
    478                 entry.txBytes = txBytes[i];
    479                 entry.txPackets = txPackets[i];
    480                 entry.operations = operations[i];
    481             } else {
    482                 // existing row, subtract remote value
    483                 entry.rxBytes = rxBytes[i] - value.rxBytes[j];
    484                 entry.rxPackets = rxPackets[i] - value.rxPackets[j];
    485                 entry.txBytes = txBytes[i] - value.txBytes[j];
    486                 entry.txPackets = txPackets[i] - value.txPackets[j];
    487                 entry.operations = operations[i] - value.operations[j];
    488                 if (enforceMonotonic
    489                         && (entry.rxBytes < 0 || entry.rxPackets < 0 || entry.txBytes < 0
    490                                 || entry.txPackets < 0 || entry.operations < 0)) {
    491                     Log.v(TAG, "lhs=" + this);
    492                     Log.v(TAG, "rhs=" + value);
    493                     throw new IllegalArgumentException(
    494                             "found non-monotonic values at lhs[" + i + "] - rhs[" + j + "]");
    495                 }
    496                 if (clampNegative) {
    497                     entry.rxBytes = Math.max(0, entry.rxBytes);
    498                     entry.rxPackets = Math.max(0, entry.rxPackets);
    499                     entry.txBytes = Math.max(0, entry.txBytes);
    500                     entry.txPackets = Math.max(0, entry.txPackets);
    501                     entry.operations = Math.max(0, entry.operations);
    502                 }
    503             }
    504 
    505             result.addValues(entry);
    506         }
    507 
    508         return result;
    509     }
    510 
    511     /**
    512      * Return total statistics grouped by {@link #iface}; doesn't mutate the
    513      * original structure.
    514      */
    515     public NetworkStats groupedByIface() {
    516         final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
    517 
    518         final Entry entry = new Entry();
    519         entry.uid = UID_ALL;
    520         entry.set = SET_ALL;
    521         entry.tag = TAG_NONE;
    522         entry.operations = 0L;
    523 
    524         for (int i = 0; i < size; i++) {
    525             // skip specific tags, since already counted in TAG_NONE
    526             if (tag[i] != TAG_NONE) continue;
    527 
    528             entry.iface = iface[i];
    529             entry.rxBytes = rxBytes[i];
    530             entry.rxPackets = rxPackets[i];
    531             entry.txBytes = txBytes[i];
    532             entry.txPackets = txPackets[i];
    533             stats.combineValues(entry);
    534         }
    535 
    536         return stats;
    537     }
    538 
    539     /**
    540      * Return total statistics grouped by {@link #uid}; doesn't mutate the
    541      * original structure.
    542      */
    543     public NetworkStats groupedByUid() {
    544         final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
    545 
    546         final Entry entry = new Entry();
    547         entry.iface = IFACE_ALL;
    548         entry.set = SET_ALL;
    549         entry.tag = TAG_NONE;
    550 
    551         for (int i = 0; i < size; i++) {
    552             // skip specific tags, since already counted in TAG_NONE
    553             if (tag[i] != TAG_NONE) continue;
    554 
    555             entry.uid = uid[i];
    556             entry.rxBytes = rxBytes[i];
    557             entry.rxPackets = rxPackets[i];
    558             entry.txBytes = txBytes[i];
    559             entry.txPackets = txPackets[i];
    560             entry.operations = operations[i];
    561             stats.combineValues(entry);
    562         }
    563 
    564         return stats;
    565     }
    566 
    567     public void dump(String prefix, PrintWriter pw) {
    568         pw.print(prefix);
    569         pw.print("NetworkStats: elapsedRealtime="); pw.println(elapsedRealtime);
    570         for (int i = 0; i < size; i++) {
    571             pw.print(prefix);
    572             pw.print("  iface="); pw.print(iface[i]);
    573             pw.print(" uid="); pw.print(uid[i]);
    574             pw.print(" set="); pw.print(setToString(set[i]));
    575             pw.print(" tag="); pw.print(tagToString(tag[i]));
    576             pw.print(" rxBytes="); pw.print(rxBytes[i]);
    577             pw.print(" rxPackets="); pw.print(rxPackets[i]);
    578             pw.print(" txBytes="); pw.print(txBytes[i]);
    579             pw.print(" txPackets="); pw.print(txPackets[i]);
    580             pw.print(" operations="); pw.println(operations[i]);
    581         }
    582     }
    583 
    584     /**
    585      * Return text description of {@link #set} value.
    586      */
    587     public static String setToString(int set) {
    588         switch (set) {
    589             case SET_ALL:
    590                 return "ALL";
    591             case SET_DEFAULT:
    592                 return "DEFAULT";
    593             case SET_FOREGROUND:
    594                 return "FOREGROUND";
    595             default:
    596                 return "UNKNOWN";
    597         }
    598     }
    599 
    600     /**
    601      * Return text description of {@link #tag} value.
    602      */
    603     public static String tagToString(int tag) {
    604         return "0x" + Integer.toHexString(tag);
    605     }
    606 
    607     @Override
    608     public String toString() {
    609         final CharArrayWriter writer = new CharArrayWriter();
    610         dump("", new PrintWriter(writer));
    611         return writer.toString();
    612     }
    613 
    614     /** {@inheritDoc} */
    615     public int describeContents() {
    616         return 0;
    617     }
    618 
    619     public static final Creator<NetworkStats> CREATOR = new Creator<NetworkStats>() {
    620         public NetworkStats createFromParcel(Parcel in) {
    621             return new NetworkStats(in);
    622         }
    623 
    624         public NetworkStats[] newArray(int size) {
    625             return new NetworkStats[size];
    626         }
    627     };
    628 }
    629