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