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 android.net.NetworkStats.IFACE_ALL;
     20 import static android.net.NetworkStats.SET_DEFAULT;
     21 import static android.net.NetworkStats.TAG_NONE;
     22 import static android.net.NetworkStats.UID_ALL;
     23 import static android.net.NetworkStatsHistory.DataStreamUtils.readFullLongArray;
     24 import static android.net.NetworkStatsHistory.DataStreamUtils.readVarLongArray;
     25 import static android.net.NetworkStatsHistory.DataStreamUtils.writeVarLongArray;
     26 import static android.net.NetworkStatsHistory.Entry.UNKNOWN;
     27 import static android.net.NetworkStatsHistory.ParcelUtils.readLongArray;
     28 import static android.net.NetworkStatsHistory.ParcelUtils.writeLongArray;
     29 import static com.android.internal.util.ArrayUtils.total;
     30 
     31 import android.os.Parcel;
     32 import android.os.Parcelable;
     33 import android.util.MathUtils;
     34 
     35 import com.android.internal.util.IndentingPrintWriter;
     36 
     37 import java.io.CharArrayWriter;
     38 import java.io.DataInputStream;
     39 import java.io.DataOutputStream;
     40 import java.io.IOException;
     41 import java.net.ProtocolException;
     42 import java.util.Arrays;
     43 import java.util.Random;
     44 
     45 /**
     46  * Collection of historical network statistics, recorded into equally-sized
     47  * "buckets" in time. Internally it stores data in {@code long} series for more
     48  * efficient persistence.
     49  * <p>
     50  * Each bucket is defined by a {@link #bucketStart} timestamp, and lasts for
     51  * {@link #bucketDuration}. Internally assumes that {@link #bucketStart} is
     52  * sorted at all times.
     53  *
     54  * @hide
     55  */
     56 public class NetworkStatsHistory implements Parcelable {
     57     private static final int VERSION_INIT = 1;
     58     private static final int VERSION_ADD_PACKETS = 2;
     59     private static final int VERSION_ADD_ACTIVE = 3;
     60 
     61     public static final int FIELD_ACTIVE_TIME = 0x01;
     62     public static final int FIELD_RX_BYTES = 0x02;
     63     public static final int FIELD_RX_PACKETS = 0x04;
     64     public static final int FIELD_TX_BYTES = 0x08;
     65     public static final int FIELD_TX_PACKETS = 0x10;
     66     public static final int FIELD_OPERATIONS = 0x20;
     67 
     68     public static final int FIELD_ALL = 0xFFFFFFFF;
     69 
     70     private long bucketDuration;
     71     private int bucketCount;
     72     private long[] bucketStart;
     73     private long[] activeTime;
     74     private long[] rxBytes;
     75     private long[] rxPackets;
     76     private long[] txBytes;
     77     private long[] txPackets;
     78     private long[] operations;
     79     private long totalBytes;
     80 
     81     public static class Entry {
     82         public static final long UNKNOWN = -1;
     83 
     84         public long bucketDuration;
     85         public long bucketStart;
     86         public long activeTime;
     87         public long rxBytes;
     88         public long rxPackets;
     89         public long txBytes;
     90         public long txPackets;
     91         public long operations;
     92     }
     93 
     94     public NetworkStatsHistory(long bucketDuration) {
     95         this(bucketDuration, 10, FIELD_ALL);
     96     }
     97 
     98     public NetworkStatsHistory(long bucketDuration, int initialSize) {
     99         this(bucketDuration, initialSize, FIELD_ALL);
    100     }
    101 
    102     public NetworkStatsHistory(long bucketDuration, int initialSize, int fields) {
    103         this.bucketDuration = bucketDuration;
    104         bucketStart = new long[initialSize];
    105         if ((fields & FIELD_ACTIVE_TIME) != 0) activeTime = new long[initialSize];
    106         if ((fields & FIELD_RX_BYTES) != 0) rxBytes = new long[initialSize];
    107         if ((fields & FIELD_RX_PACKETS) != 0) rxPackets = new long[initialSize];
    108         if ((fields & FIELD_TX_BYTES) != 0) txBytes = new long[initialSize];
    109         if ((fields & FIELD_TX_PACKETS) != 0) txPackets = new long[initialSize];
    110         if ((fields & FIELD_OPERATIONS) != 0) operations = new long[initialSize];
    111         bucketCount = 0;
    112         totalBytes = 0;
    113     }
    114 
    115     public NetworkStatsHistory(NetworkStatsHistory existing, long bucketDuration) {
    116         this(bucketDuration, existing.estimateResizeBuckets(bucketDuration));
    117         recordEntireHistory(existing);
    118     }
    119 
    120     public NetworkStatsHistory(Parcel in) {
    121         bucketDuration = in.readLong();
    122         bucketStart = readLongArray(in);
    123         activeTime = readLongArray(in);
    124         rxBytes = readLongArray(in);
    125         rxPackets = readLongArray(in);
    126         txBytes = readLongArray(in);
    127         txPackets = readLongArray(in);
    128         operations = readLongArray(in);
    129         bucketCount = bucketStart.length;
    130         totalBytes = in.readLong();
    131     }
    132 
    133     @Override
    134     public void writeToParcel(Parcel out, int flags) {
    135         out.writeLong(bucketDuration);
    136         writeLongArray(out, bucketStart, bucketCount);
    137         writeLongArray(out, activeTime, bucketCount);
    138         writeLongArray(out, rxBytes, bucketCount);
    139         writeLongArray(out, rxPackets, bucketCount);
    140         writeLongArray(out, txBytes, bucketCount);
    141         writeLongArray(out, txPackets, bucketCount);
    142         writeLongArray(out, operations, bucketCount);
    143         out.writeLong(totalBytes);
    144     }
    145 
    146     public NetworkStatsHistory(DataInputStream in) throws IOException {
    147         final int version = in.readInt();
    148         switch (version) {
    149             case VERSION_INIT: {
    150                 bucketDuration = in.readLong();
    151                 bucketStart = readFullLongArray(in);
    152                 rxBytes = readFullLongArray(in);
    153                 rxPackets = new long[bucketStart.length];
    154                 txBytes = readFullLongArray(in);
    155                 txPackets = new long[bucketStart.length];
    156                 operations = new long[bucketStart.length];
    157                 bucketCount = bucketStart.length;
    158                 totalBytes = total(rxBytes) + total(txBytes);
    159                 break;
    160             }
    161             case VERSION_ADD_PACKETS:
    162             case VERSION_ADD_ACTIVE: {
    163                 bucketDuration = in.readLong();
    164                 bucketStart = readVarLongArray(in);
    165                 activeTime = (version >= VERSION_ADD_ACTIVE) ? readVarLongArray(in)
    166                         : new long[bucketStart.length];
    167                 rxBytes = readVarLongArray(in);
    168                 rxPackets = readVarLongArray(in);
    169                 txBytes = readVarLongArray(in);
    170                 txPackets = readVarLongArray(in);
    171                 operations = readVarLongArray(in);
    172                 bucketCount = bucketStart.length;
    173                 totalBytes = total(rxBytes) + total(txBytes);
    174                 break;
    175             }
    176             default: {
    177                 throw new ProtocolException("unexpected version: " + version);
    178             }
    179         }
    180     }
    181 
    182     public void writeToStream(DataOutputStream out) throws IOException {
    183         out.writeInt(VERSION_ADD_ACTIVE);
    184         out.writeLong(bucketDuration);
    185         writeVarLongArray(out, bucketStart, bucketCount);
    186         writeVarLongArray(out, activeTime, bucketCount);
    187         writeVarLongArray(out, rxBytes, bucketCount);
    188         writeVarLongArray(out, rxPackets, bucketCount);
    189         writeVarLongArray(out, txBytes, bucketCount);
    190         writeVarLongArray(out, txPackets, bucketCount);
    191         writeVarLongArray(out, operations, bucketCount);
    192     }
    193 
    194     @Override
    195     public int describeContents() {
    196         return 0;
    197     }
    198 
    199     public int size() {
    200         return bucketCount;
    201     }
    202 
    203     public long getBucketDuration() {
    204         return bucketDuration;
    205     }
    206 
    207     public long getStart() {
    208         if (bucketCount > 0) {
    209             return bucketStart[0];
    210         } else {
    211             return Long.MAX_VALUE;
    212         }
    213     }
    214 
    215     public long getEnd() {
    216         if (bucketCount > 0) {
    217             return bucketStart[bucketCount - 1] + bucketDuration;
    218         } else {
    219             return Long.MIN_VALUE;
    220         }
    221     }
    222 
    223     /**
    224      * Return total bytes represented by this history.
    225      */
    226     public long getTotalBytes() {
    227         return totalBytes;
    228     }
    229 
    230     /**
    231      * Return index of bucket that contains or is immediately before the
    232      * requested time.
    233      */
    234     public int getIndexBefore(long time) {
    235         int index = Arrays.binarySearch(bucketStart, 0, bucketCount, time);
    236         if (index < 0) {
    237             index = (~index) - 1;
    238         } else {
    239             index -= 1;
    240         }
    241         return MathUtils.constrain(index, 0, bucketCount - 1);
    242     }
    243 
    244     /**
    245      * Return index of bucket that contains or is immediately after the
    246      * requested time.
    247      */
    248     public int getIndexAfter(long time) {
    249         int index = Arrays.binarySearch(bucketStart, 0, bucketCount, time);
    250         if (index < 0) {
    251             index = ~index;
    252         } else {
    253             index += 1;
    254         }
    255         return MathUtils.constrain(index, 0, bucketCount - 1);
    256     }
    257 
    258     /**
    259      * Return specific stats entry.
    260      */
    261     public Entry getValues(int i, Entry recycle) {
    262         final Entry entry = recycle != null ? recycle : new Entry();
    263         entry.bucketStart = bucketStart[i];
    264         entry.bucketDuration = bucketDuration;
    265         entry.activeTime = getLong(activeTime, i, UNKNOWN);
    266         entry.rxBytes = getLong(rxBytes, i, UNKNOWN);
    267         entry.rxPackets = getLong(rxPackets, i, UNKNOWN);
    268         entry.txBytes = getLong(txBytes, i, UNKNOWN);
    269         entry.txPackets = getLong(txPackets, i, UNKNOWN);
    270         entry.operations = getLong(operations, i, UNKNOWN);
    271         return entry;
    272     }
    273 
    274     /**
    275      * Record that data traffic occurred in the given time range. Will
    276      * distribute across internal buckets, creating new buckets as needed.
    277      */
    278     @Deprecated
    279     public void recordData(long start, long end, long rxBytes, long txBytes) {
    280         recordData(start, end, new NetworkStats.Entry(
    281                 IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, 0L, txBytes, 0L, 0L));
    282     }
    283 
    284     /**
    285      * Record that data traffic occurred in the given time range. Will
    286      * distribute across internal buckets, creating new buckets as needed.
    287      */
    288     public void recordData(long start, long end, NetworkStats.Entry entry) {
    289         long rxBytes = entry.rxBytes;
    290         long rxPackets = entry.rxPackets;
    291         long txBytes = entry.txBytes;
    292         long txPackets = entry.txPackets;
    293         long operations = entry.operations;
    294 
    295         if (entry.isNegative()) {
    296             throw new IllegalArgumentException("tried recording negative data");
    297         }
    298         if (entry.isEmpty()) {
    299             return;
    300         }
    301 
    302         // create any buckets needed by this range
    303         ensureBuckets(start, end);
    304 
    305         // distribute data usage into buckets
    306         long duration = end - start;
    307         final int startIndex = getIndexAfter(end);
    308         for (int i = startIndex; i >= 0; i--) {
    309             final long curStart = bucketStart[i];
    310             final long curEnd = curStart + bucketDuration;
    311 
    312             // bucket is older than record; we're finished
    313             if (curEnd < start) break;
    314             // bucket is newer than record; keep looking
    315             if (curStart > end) continue;
    316 
    317             final long overlap = Math.min(curEnd, end) - Math.max(curStart, start);
    318             if (overlap <= 0) continue;
    319 
    320             // integer math each time is faster than floating point
    321             final long fracRxBytes = rxBytes * overlap / duration;
    322             final long fracRxPackets = rxPackets * overlap / duration;
    323             final long fracTxBytes = txBytes * overlap / duration;
    324             final long fracTxPackets = txPackets * overlap / duration;
    325             final long fracOperations = operations * overlap / duration;
    326 
    327             addLong(activeTime, i, overlap);
    328             addLong(this.rxBytes, i, fracRxBytes); rxBytes -= fracRxBytes;
    329             addLong(this.rxPackets, i, fracRxPackets); rxPackets -= fracRxPackets;
    330             addLong(this.txBytes, i, fracTxBytes); txBytes -= fracTxBytes;
    331             addLong(this.txPackets, i, fracTxPackets); txPackets -= fracTxPackets;
    332             addLong(this.operations, i, fracOperations); operations -= fracOperations;
    333 
    334             duration -= overlap;
    335         }
    336 
    337         totalBytes += entry.rxBytes + entry.txBytes;
    338     }
    339 
    340     /**
    341      * Record an entire {@link NetworkStatsHistory} into this history. Usually
    342      * for combining together stats for external reporting.
    343      */
    344     public void recordEntireHistory(NetworkStatsHistory input) {
    345         recordHistory(input, Long.MIN_VALUE, Long.MAX_VALUE);
    346     }
    347 
    348     /**
    349      * Record given {@link NetworkStatsHistory} into this history, copying only
    350      * buckets that atomically occur in the inclusive time range. Doesn't
    351      * interpolate across partial buckets.
    352      */
    353     public void recordHistory(NetworkStatsHistory input, long start, long end) {
    354         final NetworkStats.Entry entry = new NetworkStats.Entry(
    355                 IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
    356         for (int i = 0; i < input.bucketCount; i++) {
    357             final long bucketStart = input.bucketStart[i];
    358             final long bucketEnd = bucketStart + input.bucketDuration;
    359 
    360             // skip when bucket is outside requested range
    361             if (bucketStart < start || bucketEnd > end) continue;
    362 
    363             entry.rxBytes = getLong(input.rxBytes, i, 0L);
    364             entry.rxPackets = getLong(input.rxPackets, i, 0L);
    365             entry.txBytes = getLong(input.txBytes, i, 0L);
    366             entry.txPackets = getLong(input.txPackets, i, 0L);
    367             entry.operations = getLong(input.operations, i, 0L);
    368 
    369             recordData(bucketStart, bucketEnd, entry);
    370         }
    371     }
    372 
    373     /**
    374      * Ensure that buckets exist for given time range, creating as needed.
    375      */
    376     private void ensureBuckets(long start, long end) {
    377         // normalize incoming range to bucket boundaries
    378         start -= start % bucketDuration;
    379         end += (bucketDuration - (end % bucketDuration)) % bucketDuration;
    380 
    381         for (long now = start; now < end; now += bucketDuration) {
    382             // try finding existing bucket
    383             final int index = Arrays.binarySearch(bucketStart, 0, bucketCount, now);
    384             if (index < 0) {
    385                 // bucket missing, create and insert
    386                 insertBucket(~index, now);
    387             }
    388         }
    389     }
    390 
    391     /**
    392      * Insert new bucket at requested index and starting time.
    393      */
    394     private void insertBucket(int index, long start) {
    395         // create more buckets when needed
    396         if (bucketCount >= bucketStart.length) {
    397             final int newLength = Math.max(bucketStart.length, 10) * 3 / 2;
    398             bucketStart = Arrays.copyOf(bucketStart, newLength);
    399             if (activeTime != null) activeTime = Arrays.copyOf(activeTime, newLength);
    400             if (rxBytes != null) rxBytes = Arrays.copyOf(rxBytes, newLength);
    401             if (rxPackets != null) rxPackets = Arrays.copyOf(rxPackets, newLength);
    402             if (txBytes != null) txBytes = Arrays.copyOf(txBytes, newLength);
    403             if (txPackets != null) txPackets = Arrays.copyOf(txPackets, newLength);
    404             if (operations != null) operations = Arrays.copyOf(operations, newLength);
    405         }
    406 
    407         // create gap when inserting bucket in middle
    408         if (index < bucketCount) {
    409             final int dstPos = index + 1;
    410             final int length = bucketCount - index;
    411 
    412             System.arraycopy(bucketStart, index, bucketStart, dstPos, length);
    413             if (activeTime != null) System.arraycopy(activeTime, index, activeTime, dstPos, length);
    414             if (rxBytes != null) System.arraycopy(rxBytes, index, rxBytes, dstPos, length);
    415             if (rxPackets != null) System.arraycopy(rxPackets, index, rxPackets, dstPos, length);
    416             if (txBytes != null) System.arraycopy(txBytes, index, txBytes, dstPos, length);
    417             if (txPackets != null) System.arraycopy(txPackets, index, txPackets, dstPos, length);
    418             if (operations != null) System.arraycopy(operations, index, operations, dstPos, length);
    419         }
    420 
    421         bucketStart[index] = start;
    422         setLong(activeTime, index, 0L);
    423         setLong(rxBytes, index, 0L);
    424         setLong(rxPackets, index, 0L);
    425         setLong(txBytes, index, 0L);
    426         setLong(txPackets, index, 0L);
    427         setLong(operations, index, 0L);
    428         bucketCount++;
    429     }
    430 
    431     /**
    432      * Remove buckets older than requested cutoff.
    433      */
    434     @Deprecated
    435     public void removeBucketsBefore(long cutoff) {
    436         int i;
    437         for (i = 0; i < bucketCount; i++) {
    438             final long curStart = bucketStart[i];
    439             final long curEnd = curStart + bucketDuration;
    440 
    441             // cutoff happens before or during this bucket; everything before
    442             // this bucket should be removed.
    443             if (curEnd > cutoff) break;
    444         }
    445 
    446         if (i > 0) {
    447             final int length = bucketStart.length;
    448             bucketStart = Arrays.copyOfRange(bucketStart, i, length);
    449             if (activeTime != null) activeTime = Arrays.copyOfRange(activeTime, i, length);
    450             if (rxBytes != null) rxBytes = Arrays.copyOfRange(rxBytes, i, length);
    451             if (rxPackets != null) rxPackets = Arrays.copyOfRange(rxPackets, i, length);
    452             if (txBytes != null) txBytes = Arrays.copyOfRange(txBytes, i, length);
    453             if (txPackets != null) txPackets = Arrays.copyOfRange(txPackets, i, length);
    454             if (operations != null) operations = Arrays.copyOfRange(operations, i, length);
    455             bucketCount -= i;
    456 
    457             // TODO: subtract removed values from totalBytes
    458         }
    459     }
    460 
    461     /**
    462      * Return interpolated data usage across the requested range. Interpolates
    463      * across buckets, so values may be rounded slightly.
    464      */
    465     public Entry getValues(long start, long end, Entry recycle) {
    466         return getValues(start, end, Long.MAX_VALUE, recycle);
    467     }
    468 
    469     /**
    470      * Return interpolated data usage across the requested range. Interpolates
    471      * across buckets, so values may be rounded slightly.
    472      */
    473     public Entry getValues(long start, long end, long now, Entry recycle) {
    474         final Entry entry = recycle != null ? recycle : new Entry();
    475         entry.bucketDuration = end - start;
    476         entry.bucketStart = start;
    477         entry.activeTime = activeTime != null ? 0 : UNKNOWN;
    478         entry.rxBytes = rxBytes != null ? 0 : UNKNOWN;
    479         entry.rxPackets = rxPackets != null ? 0 : UNKNOWN;
    480         entry.txBytes = txBytes != null ? 0 : UNKNOWN;
    481         entry.txPackets = txPackets != null ? 0 : UNKNOWN;
    482         entry.operations = operations != null ? 0 : UNKNOWN;
    483 
    484         final int startIndex = getIndexAfter(end);
    485         for (int i = startIndex; i >= 0; i--) {
    486             final long curStart = bucketStart[i];
    487             final long curEnd = curStart + bucketDuration;
    488 
    489             // bucket is older than request; we're finished
    490             if (curEnd <= start) break;
    491             // bucket is newer than request; keep looking
    492             if (curStart >= end) continue;
    493 
    494             // include full value for active buckets, otherwise only fractional
    495             final boolean activeBucket = curStart < now && curEnd > now;
    496             final long overlap;
    497             if (activeBucket) {
    498                 overlap = bucketDuration;
    499             } else {
    500                 final long overlapEnd = curEnd < end ? curEnd : end;
    501                 final long overlapStart = curStart > start ? curStart : start;
    502                 overlap = overlapEnd - overlapStart;
    503             }
    504             if (overlap <= 0) continue;
    505 
    506             // integer math each time is faster than floating point
    507             if (activeTime != null) entry.activeTime += activeTime[i] * overlap / bucketDuration;
    508             if (rxBytes != null) entry.rxBytes += rxBytes[i] * overlap / bucketDuration;
    509             if (rxPackets != null) entry.rxPackets += rxPackets[i] * overlap / bucketDuration;
    510             if (txBytes != null) entry.txBytes += txBytes[i] * overlap / bucketDuration;
    511             if (txPackets != null) entry.txPackets += txPackets[i] * overlap / bucketDuration;
    512             if (operations != null) entry.operations += operations[i] * overlap / bucketDuration;
    513         }
    514         return entry;
    515     }
    516 
    517     /**
    518      * @deprecated only for temporary testing
    519      */
    520     @Deprecated
    521     public void generateRandom(long start, long end, long bytes) {
    522         final Random r = new Random();
    523 
    524         final float fractionRx = r.nextFloat();
    525         final long rxBytes = (long) (bytes * fractionRx);
    526         final long txBytes = (long) (bytes * (1 - fractionRx));
    527 
    528         final long rxPackets = rxBytes / 1024;
    529         final long txPackets = txBytes / 1024;
    530         final long operations = rxBytes / 2048;
    531 
    532         generateRandom(start, end, rxBytes, rxPackets, txBytes, txPackets, operations, r);
    533     }
    534 
    535     /**
    536      * @deprecated only for temporary testing
    537      */
    538     @Deprecated
    539     public void generateRandom(long start, long end, long rxBytes, long rxPackets, long txBytes,
    540             long txPackets, long operations, Random r) {
    541         ensureBuckets(start, end);
    542 
    543         final NetworkStats.Entry entry = new NetworkStats.Entry(
    544                 IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
    545         while (rxBytes > 1024 || rxPackets > 128 || txBytes > 1024 || txPackets > 128
    546                 || operations > 32) {
    547             final long curStart = randomLong(r, start, end);
    548             final long curEnd = curStart + randomLong(r, 0, (end - curStart) / 2);
    549 
    550             entry.rxBytes = randomLong(r, 0, rxBytes);
    551             entry.rxPackets = randomLong(r, 0, rxPackets);
    552             entry.txBytes = randomLong(r, 0, txBytes);
    553             entry.txPackets = randomLong(r, 0, txPackets);
    554             entry.operations = randomLong(r, 0, operations);
    555 
    556             rxBytes -= entry.rxBytes;
    557             rxPackets -= entry.rxPackets;
    558             txBytes -= entry.txBytes;
    559             txPackets -= entry.txPackets;
    560             operations -= entry.operations;
    561 
    562             recordData(curStart, curEnd, entry);
    563         }
    564     }
    565 
    566     public static long randomLong(Random r, long start, long end) {
    567         return (long) (start + (r.nextFloat() * (end - start)));
    568     }
    569 
    570     public void dump(IndentingPrintWriter pw, boolean fullHistory) {
    571         pw.print("NetworkStatsHistory: bucketDuration="); pw.println(bucketDuration);
    572         pw.increaseIndent();
    573 
    574         final int start = fullHistory ? 0 : Math.max(0, bucketCount - 32);
    575         if (start > 0) {
    576             pw.print("(omitting "); pw.print(start); pw.println(" buckets)");
    577         }
    578 
    579         for (int i = start; i < bucketCount; i++) {
    580             pw.print("bucketStart="); pw.print(bucketStart[i]);
    581             if (activeTime != null) { pw.print(" activeTime="); pw.print(activeTime[i]); }
    582             if (rxBytes != null) { pw.print(" rxBytes="); pw.print(rxBytes[i]); }
    583             if (rxPackets != null) { pw.print(" rxPackets="); pw.print(rxPackets[i]); }
    584             if (txBytes != null) { pw.print(" txBytes="); pw.print(txBytes[i]); }
    585             if (txPackets != null) { pw.print(" txPackets="); pw.print(txPackets[i]); }
    586             if (operations != null) { pw.print(" operations="); pw.print(operations[i]); }
    587             pw.println();
    588         }
    589 
    590         pw.decreaseIndent();
    591     }
    592 
    593     @Override
    594     public String toString() {
    595         final CharArrayWriter writer = new CharArrayWriter();
    596         dump(new IndentingPrintWriter(writer, "  "), false);
    597         return writer.toString();
    598     }
    599 
    600     public static final Creator<NetworkStatsHistory> CREATOR = new Creator<NetworkStatsHistory>() {
    601         @Override
    602         public NetworkStatsHistory createFromParcel(Parcel in) {
    603             return new NetworkStatsHistory(in);
    604         }
    605 
    606         @Override
    607         public NetworkStatsHistory[] newArray(int size) {
    608             return new NetworkStatsHistory[size];
    609         }
    610     };
    611 
    612     private static long getLong(long[] array, int i, long value) {
    613         return array != null ? array[i] : value;
    614     }
    615 
    616     private static void setLong(long[] array, int i, long value) {
    617         if (array != null) array[i] = value;
    618     }
    619 
    620     private static void addLong(long[] array, int i, long value) {
    621         if (array != null) array[i] += value;
    622     }
    623 
    624     public int estimateResizeBuckets(long newBucketDuration) {
    625         return (int) (size() * getBucketDuration() / newBucketDuration);
    626     }
    627 
    628     /**
    629      * Utility methods for interacting with {@link DataInputStream} and
    630      * {@link DataOutputStream}, mostly dealing with writing partial arrays.
    631      */
    632     public static class DataStreamUtils {
    633         @Deprecated
    634         public static long[] readFullLongArray(DataInputStream in) throws IOException {
    635             final int size = in.readInt();
    636             final long[] values = new long[size];
    637             for (int i = 0; i < values.length; i++) {
    638                 values[i] = in.readLong();
    639             }
    640             return values;
    641         }
    642 
    643         /**
    644          * Read variable-length {@link Long} using protobuf-style approach.
    645          */
    646         public static long readVarLong(DataInputStream in) throws IOException {
    647             int shift = 0;
    648             long result = 0;
    649             while (shift < 64) {
    650                 byte b = in.readByte();
    651                 result |= (long) (b & 0x7F) << shift;
    652                 if ((b & 0x80) == 0)
    653                     return result;
    654                 shift += 7;
    655             }
    656             throw new ProtocolException("malformed long");
    657         }
    658 
    659         /**
    660          * Write variable-length {@link Long} using protobuf-style approach.
    661          */
    662         public static void writeVarLong(DataOutputStream out, long value) throws IOException {
    663             while (true) {
    664                 if ((value & ~0x7FL) == 0) {
    665                     out.writeByte((int) value);
    666                     return;
    667                 } else {
    668                     out.writeByte(((int) value & 0x7F) | 0x80);
    669                     value >>>= 7;
    670                 }
    671             }
    672         }
    673 
    674         public static long[] readVarLongArray(DataInputStream in) throws IOException {
    675             final int size = in.readInt();
    676             if (size == -1) return null;
    677             final long[] values = new long[size];
    678             for (int i = 0; i < values.length; i++) {
    679                 values[i] = readVarLong(in);
    680             }
    681             return values;
    682         }
    683 
    684         public static void writeVarLongArray(DataOutputStream out, long[] values, int size)
    685                 throws IOException {
    686             if (values == null) {
    687                 out.writeInt(-1);
    688                 return;
    689             }
    690             if (size > values.length) {
    691                 throw new IllegalArgumentException("size larger than length");
    692             }
    693             out.writeInt(size);
    694             for (int i = 0; i < size; i++) {
    695                 writeVarLong(out, values[i]);
    696             }
    697         }
    698     }
    699 
    700     /**
    701      * Utility methods for interacting with {@link Parcel} structures, mostly
    702      * dealing with writing partial arrays.
    703      */
    704     public static class ParcelUtils {
    705         public static long[] readLongArray(Parcel in) {
    706             final int size = in.readInt();
    707             if (size == -1) return null;
    708             final long[] values = new long[size];
    709             for (int i = 0; i < values.length; i++) {
    710                 values[i] = in.readLong();
    711             }
    712             return values;
    713         }
    714 
    715         public static void writeLongArray(Parcel out, long[] values, int size) {
    716             if (values == null) {
    717                 out.writeInt(-1);
    718                 return;
    719             }
    720             if (size > values.length) {
    721                 throw new IllegalArgumentException("size larger than length");
    722             }
    723             out.writeInt(size);
    724             for (int i = 0; i < size; i++) {
    725                 out.writeLong(values[i]);
    726             }
    727         }
    728     }
    729 
    730 }
    731