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