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         if (bucketStart.length != bucketCount || rxBytes.length != bucketCount
    182                 || rxPackets.length != bucketCount || txBytes.length != bucketCount
    183                 || txPackets.length != bucketCount || operations.length != bucketCount) {
    184             throw new ProtocolException("Mismatched history lengths");
    185         }
    186     }
    187 
    188     public void writeToStream(DataOutputStream out) throws IOException {
    189         out.writeInt(VERSION_ADD_ACTIVE);
    190         out.writeLong(bucketDuration);
    191         writeVarLongArray(out, bucketStart, bucketCount);
    192         writeVarLongArray(out, activeTime, bucketCount);
    193         writeVarLongArray(out, rxBytes, bucketCount);
    194         writeVarLongArray(out, rxPackets, bucketCount);
    195         writeVarLongArray(out, txBytes, bucketCount);
    196         writeVarLongArray(out, txPackets, bucketCount);
    197         writeVarLongArray(out, operations, bucketCount);
    198     }
    199 
    200     @Override
    201     public int describeContents() {
    202         return 0;
    203     }
    204 
    205     public int size() {
    206         return bucketCount;
    207     }
    208 
    209     public long getBucketDuration() {
    210         return bucketDuration;
    211     }
    212 
    213     public long getStart() {
    214         if (bucketCount > 0) {
    215             return bucketStart[0];
    216         } else {
    217             return Long.MAX_VALUE;
    218         }
    219     }
    220 
    221     public long getEnd() {
    222         if (bucketCount > 0) {
    223             return bucketStart[bucketCount - 1] + bucketDuration;
    224         } else {
    225             return Long.MIN_VALUE;
    226         }
    227     }
    228 
    229     /**
    230      * Return total bytes represented by this history.
    231      */
    232     public long getTotalBytes() {
    233         return totalBytes;
    234     }
    235 
    236     /**
    237      * Return index of bucket that contains or is immediately before the
    238      * requested time.
    239      */
    240     public int getIndexBefore(long time) {
    241         int index = Arrays.binarySearch(bucketStart, 0, bucketCount, time);
    242         if (index < 0) {
    243             index = (~index) - 1;
    244         } else {
    245             index -= 1;
    246         }
    247         return MathUtils.constrain(index, 0, bucketCount - 1);
    248     }
    249 
    250     /**
    251      * Return index of bucket that contains or is immediately after the
    252      * requested time.
    253      */
    254     public int getIndexAfter(long time) {
    255         int index = Arrays.binarySearch(bucketStart, 0, bucketCount, time);
    256         if (index < 0) {
    257             index = ~index;
    258         } else {
    259             index += 1;
    260         }
    261         return MathUtils.constrain(index, 0, bucketCount - 1);
    262     }
    263 
    264     /**
    265      * Return specific stats entry.
    266      */
    267     public Entry getValues(int i, Entry recycle) {
    268         final Entry entry = recycle != null ? recycle : new Entry();
    269         entry.bucketStart = bucketStart[i];
    270         entry.bucketDuration = bucketDuration;
    271         entry.activeTime = getLong(activeTime, i, UNKNOWN);
    272         entry.rxBytes = getLong(rxBytes, i, UNKNOWN);
    273         entry.rxPackets = getLong(rxPackets, i, UNKNOWN);
    274         entry.txBytes = getLong(txBytes, i, UNKNOWN);
    275         entry.txPackets = getLong(txPackets, i, UNKNOWN);
    276         entry.operations = getLong(operations, i, UNKNOWN);
    277         return entry;
    278     }
    279 
    280     /**
    281      * Record that data traffic occurred in the given time range. Will
    282      * distribute across internal buckets, creating new buckets as needed.
    283      */
    284     @Deprecated
    285     public void recordData(long start, long end, long rxBytes, long txBytes) {
    286         recordData(start, end, new NetworkStats.Entry(
    287                 IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, 0L, txBytes, 0L, 0L));
    288     }
    289 
    290     /**
    291      * Record that data traffic occurred in the given time range. Will
    292      * distribute across internal buckets, creating new buckets as needed.
    293      */
    294     public void recordData(long start, long end, NetworkStats.Entry entry) {
    295         long rxBytes = entry.rxBytes;
    296         long rxPackets = entry.rxPackets;
    297         long txBytes = entry.txBytes;
    298         long txPackets = entry.txPackets;
    299         long operations = entry.operations;
    300 
    301         if (entry.isNegative()) {
    302             throw new IllegalArgumentException("tried recording negative data");
    303         }
    304         if (entry.isEmpty()) {
    305             return;
    306         }
    307 
    308         // create any buckets needed by this range
    309         ensureBuckets(start, end);
    310 
    311         // distribute data usage into buckets
    312         long duration = end - start;
    313         final int startIndex = getIndexAfter(end);
    314         for (int i = startIndex; i >= 0; i--) {
    315             final long curStart = bucketStart[i];
    316             final long curEnd = curStart + bucketDuration;
    317 
    318             // bucket is older than record; we're finished
    319             if (curEnd < start) break;
    320             // bucket is newer than record; keep looking
    321             if (curStart > end) continue;
    322 
    323             final long overlap = Math.min(curEnd, end) - Math.max(curStart, start);
    324             if (overlap <= 0) continue;
    325 
    326             // integer math each time is faster than floating point
    327             final long fracRxBytes = rxBytes * overlap / duration;
    328             final long fracRxPackets = rxPackets * overlap / duration;
    329             final long fracTxBytes = txBytes * overlap / duration;
    330             final long fracTxPackets = txPackets * overlap / duration;
    331             final long fracOperations = operations * overlap / duration;
    332 
    333             addLong(activeTime, i, overlap);
    334             addLong(this.rxBytes, i, fracRxBytes); rxBytes -= fracRxBytes;
    335             addLong(this.rxPackets, i, fracRxPackets); rxPackets -= fracRxPackets;
    336             addLong(this.txBytes, i, fracTxBytes); txBytes -= fracTxBytes;
    337             addLong(this.txPackets, i, fracTxPackets); txPackets -= fracTxPackets;
    338             addLong(this.operations, i, fracOperations); operations -= fracOperations;
    339 
    340             duration -= overlap;
    341         }
    342 
    343         totalBytes += entry.rxBytes + entry.txBytes;
    344     }
    345 
    346     /**
    347      * Record an entire {@link NetworkStatsHistory} into this history. Usually
    348      * for combining together stats for external reporting.
    349      */
    350     public void recordEntireHistory(NetworkStatsHistory input) {
    351         recordHistory(input, Long.MIN_VALUE, Long.MAX_VALUE);
    352     }
    353 
    354     /**
    355      * Record given {@link NetworkStatsHistory} into this history, copying only
    356      * buckets that atomically occur in the inclusive time range. Doesn't
    357      * interpolate across partial buckets.
    358      */
    359     public void recordHistory(NetworkStatsHistory input, long start, long end) {
    360         final NetworkStats.Entry entry = new NetworkStats.Entry(
    361                 IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
    362         for (int i = 0; i < input.bucketCount; i++) {
    363             final long bucketStart = input.bucketStart[i];
    364             final long bucketEnd = bucketStart + input.bucketDuration;
    365 
    366             // skip when bucket is outside requested range
    367             if (bucketStart < start || bucketEnd > end) continue;
    368 
    369             entry.rxBytes = getLong(input.rxBytes, i, 0L);
    370             entry.rxPackets = getLong(input.rxPackets, i, 0L);
    371             entry.txBytes = getLong(input.txBytes, i, 0L);
    372             entry.txPackets = getLong(input.txPackets, i, 0L);
    373             entry.operations = getLong(input.operations, i, 0L);
    374 
    375             recordData(bucketStart, bucketEnd, entry);
    376         }
    377     }
    378 
    379     /**
    380      * Ensure that buckets exist for given time range, creating as needed.
    381      */
    382     private void ensureBuckets(long start, long end) {
    383         // normalize incoming range to bucket boundaries
    384         start -= start % bucketDuration;
    385         end += (bucketDuration - (end % bucketDuration)) % bucketDuration;
    386 
    387         for (long now = start; now < end; now += bucketDuration) {
    388             // try finding existing bucket
    389             final int index = Arrays.binarySearch(bucketStart, 0, bucketCount, now);
    390             if (index < 0) {
    391                 // bucket missing, create and insert
    392                 insertBucket(~index, now);
    393             }
    394         }
    395     }
    396 
    397     /**
    398      * Insert new bucket at requested index and starting time.
    399      */
    400     private void insertBucket(int index, long start) {
    401         // create more buckets when needed
    402         if (bucketCount >= bucketStart.length) {
    403             final int newLength = Math.max(bucketStart.length, 10) * 3 / 2;
    404             bucketStart = Arrays.copyOf(bucketStart, newLength);
    405             if (activeTime != null) activeTime = Arrays.copyOf(activeTime, newLength);
    406             if (rxBytes != null) rxBytes = Arrays.copyOf(rxBytes, newLength);
    407             if (rxPackets != null) rxPackets = Arrays.copyOf(rxPackets, newLength);
    408             if (txBytes != null) txBytes = Arrays.copyOf(txBytes, newLength);
    409             if (txPackets != null) txPackets = Arrays.copyOf(txPackets, newLength);
    410             if (operations != null) operations = Arrays.copyOf(operations, newLength);
    411         }
    412 
    413         // create gap when inserting bucket in middle
    414         if (index < bucketCount) {
    415             final int dstPos = index + 1;
    416             final int length = bucketCount - index;
    417 
    418             System.arraycopy(bucketStart, index, bucketStart, dstPos, length);
    419             if (activeTime != null) System.arraycopy(activeTime, index, activeTime, dstPos, length);
    420             if (rxBytes != null) System.arraycopy(rxBytes, index, rxBytes, dstPos, length);
    421             if (rxPackets != null) System.arraycopy(rxPackets, index, rxPackets, dstPos, length);
    422             if (txBytes != null) System.arraycopy(txBytes, index, txBytes, dstPos, length);
    423             if (txPackets != null) System.arraycopy(txPackets, index, txPackets, dstPos, length);
    424             if (operations != null) System.arraycopy(operations, index, operations, dstPos, length);
    425         }
    426 
    427         bucketStart[index] = start;
    428         setLong(activeTime, index, 0L);
    429         setLong(rxBytes, index, 0L);
    430         setLong(rxPackets, index, 0L);
    431         setLong(txBytes, index, 0L);
    432         setLong(txPackets, index, 0L);
    433         setLong(operations, index, 0L);
    434         bucketCount++;
    435     }
    436 
    437     /**
    438      * Remove buckets older than requested cutoff.
    439      */
    440     @Deprecated
    441     public void removeBucketsBefore(long cutoff) {
    442         int i;
    443         for (i = 0; i < bucketCount; i++) {
    444             final long curStart = bucketStart[i];
    445             final long curEnd = curStart + bucketDuration;
    446 
    447             // cutoff happens before or during this bucket; everything before
    448             // this bucket should be removed.
    449             if (curEnd > cutoff) break;
    450         }
    451 
    452         if (i > 0) {
    453             final int length = bucketStart.length;
    454             bucketStart = Arrays.copyOfRange(bucketStart, i, length);
    455             if (activeTime != null) activeTime = Arrays.copyOfRange(activeTime, i, length);
    456             if (rxBytes != null) rxBytes = Arrays.copyOfRange(rxBytes, i, length);
    457             if (rxPackets != null) rxPackets = Arrays.copyOfRange(rxPackets, i, length);
    458             if (txBytes != null) txBytes = Arrays.copyOfRange(txBytes, i, length);
    459             if (txPackets != null) txPackets = Arrays.copyOfRange(txPackets, i, length);
    460             if (operations != null) operations = Arrays.copyOfRange(operations, i, length);
    461             bucketCount -= i;
    462 
    463             // TODO: subtract removed values from totalBytes
    464         }
    465     }
    466 
    467     /**
    468      * Return interpolated data usage across the requested range. Interpolates
    469      * across buckets, so values may be rounded slightly.
    470      */
    471     public Entry getValues(long start, long end, Entry recycle) {
    472         return getValues(start, end, Long.MAX_VALUE, recycle);
    473     }
    474 
    475     /**
    476      * Return interpolated data usage across the requested range. Interpolates
    477      * across buckets, so values may be rounded slightly.
    478      */
    479     public Entry getValues(long start, long end, long now, Entry recycle) {
    480         final Entry entry = recycle != null ? recycle : new Entry();
    481         entry.bucketDuration = end - start;
    482         entry.bucketStart = start;
    483         entry.activeTime = activeTime != null ? 0 : UNKNOWN;
    484         entry.rxBytes = rxBytes != null ? 0 : UNKNOWN;
    485         entry.rxPackets = rxPackets != null ? 0 : UNKNOWN;
    486         entry.txBytes = txBytes != null ? 0 : UNKNOWN;
    487         entry.txPackets = txPackets != null ? 0 : UNKNOWN;
    488         entry.operations = operations != null ? 0 : UNKNOWN;
    489 
    490         final int startIndex = getIndexAfter(end);
    491         for (int i = startIndex; i >= 0; i--) {
    492             final long curStart = bucketStart[i];
    493             final long curEnd = curStart + bucketDuration;
    494 
    495             // bucket is older than request; we're finished
    496             if (curEnd <= start) break;
    497             // bucket is newer than request; keep looking
    498             if (curStart >= end) continue;
    499 
    500             // include full value for active buckets, otherwise only fractional
    501             final boolean activeBucket = curStart < now && curEnd > now;
    502             final long overlap;
    503             if (activeBucket) {
    504                 overlap = bucketDuration;
    505             } else {
    506                 final long overlapEnd = curEnd < end ? curEnd : end;
    507                 final long overlapStart = curStart > start ? curStart : start;
    508                 overlap = overlapEnd - overlapStart;
    509             }
    510             if (overlap <= 0) continue;
    511 
    512             // integer math each time is faster than floating point
    513             if (activeTime != null) entry.activeTime += activeTime[i] * overlap / bucketDuration;
    514             if (rxBytes != null) entry.rxBytes += rxBytes[i] * overlap / bucketDuration;
    515             if (rxPackets != null) entry.rxPackets += rxPackets[i] * overlap / bucketDuration;
    516             if (txBytes != null) entry.txBytes += txBytes[i] * overlap / bucketDuration;
    517             if (txPackets != null) entry.txPackets += txPackets[i] * overlap / bucketDuration;
    518             if (operations != null) entry.operations += operations[i] * overlap / bucketDuration;
    519         }
    520         return entry;
    521     }
    522 
    523     /**
    524      * @deprecated only for temporary testing
    525      */
    526     @Deprecated
    527     public void generateRandom(long start, long end, long bytes) {
    528         final Random r = new Random();
    529 
    530         final float fractionRx = r.nextFloat();
    531         final long rxBytes = (long) (bytes * fractionRx);
    532         final long txBytes = (long) (bytes * (1 - fractionRx));
    533 
    534         final long rxPackets = rxBytes / 1024;
    535         final long txPackets = txBytes / 1024;
    536         final long operations = rxBytes / 2048;
    537 
    538         generateRandom(start, end, rxBytes, rxPackets, txBytes, txPackets, operations, r);
    539     }
    540 
    541     /**
    542      * @deprecated only for temporary testing
    543      */
    544     @Deprecated
    545     public void generateRandom(long start, long end, long rxBytes, long rxPackets, long txBytes,
    546             long txPackets, long operations, Random r) {
    547         ensureBuckets(start, end);
    548 
    549         final NetworkStats.Entry entry = new NetworkStats.Entry(
    550                 IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
    551         while (rxBytes > 1024 || rxPackets > 128 || txBytes > 1024 || txPackets > 128
    552                 || operations > 32) {
    553             final long curStart = randomLong(r, start, end);
    554             final long curEnd = curStart + randomLong(r, 0, (end - curStart) / 2);
    555 
    556             entry.rxBytes = randomLong(r, 0, rxBytes);
    557             entry.rxPackets = randomLong(r, 0, rxPackets);
    558             entry.txBytes = randomLong(r, 0, txBytes);
    559             entry.txPackets = randomLong(r, 0, txPackets);
    560             entry.operations = randomLong(r, 0, operations);
    561 
    562             rxBytes -= entry.rxBytes;
    563             rxPackets -= entry.rxPackets;
    564             txBytes -= entry.txBytes;
    565             txPackets -= entry.txPackets;
    566             operations -= entry.operations;
    567 
    568             recordData(curStart, curEnd, entry);
    569         }
    570     }
    571 
    572     public static long randomLong(Random r, long start, long end) {
    573         return (long) (start + (r.nextFloat() * (end - start)));
    574     }
    575 
    576     public void dump(IndentingPrintWriter pw, boolean fullHistory) {
    577         pw.print("NetworkStatsHistory: bucketDuration="); pw.println(bucketDuration);
    578         pw.increaseIndent();
    579 
    580         final int start = fullHistory ? 0 : Math.max(0, bucketCount - 32);
    581         if (start > 0) {
    582             pw.print("(omitting "); pw.print(start); pw.println(" buckets)");
    583         }
    584 
    585         for (int i = start; i < bucketCount; i++) {
    586             pw.print("bucketStart="); pw.print(bucketStart[i]);
    587             if (activeTime != null) { pw.print(" activeTime="); pw.print(activeTime[i]); }
    588             if (rxBytes != null) { pw.print(" rxBytes="); pw.print(rxBytes[i]); }
    589             if (rxPackets != null) { pw.print(" rxPackets="); pw.print(rxPackets[i]); }
    590             if (txBytes != null) { pw.print(" txBytes="); pw.print(txBytes[i]); }
    591             if (txPackets != null) { pw.print(" txPackets="); pw.print(txPackets[i]); }
    592             if (operations != null) { pw.print(" operations="); pw.print(operations[i]); }
    593             pw.println();
    594         }
    595 
    596         pw.decreaseIndent();
    597     }
    598 
    599     @Override
    600     public String toString() {
    601         final CharArrayWriter writer = new CharArrayWriter();
    602         dump(new IndentingPrintWriter(writer, "  "), false);
    603         return writer.toString();
    604     }
    605 
    606     public static final Creator<NetworkStatsHistory> CREATOR = new Creator<NetworkStatsHistory>() {
    607         @Override
    608         public NetworkStatsHistory createFromParcel(Parcel in) {
    609             return new NetworkStatsHistory(in);
    610         }
    611 
    612         @Override
    613         public NetworkStatsHistory[] newArray(int size) {
    614             return new NetworkStatsHistory[size];
    615         }
    616     };
    617 
    618     private static long getLong(long[] array, int i, long value) {
    619         return array != null ? array[i] : value;
    620     }
    621 
    622     private static void setLong(long[] array, int i, long value) {
    623         if (array != null) array[i] = value;
    624     }
    625 
    626     private static void addLong(long[] array, int i, long value) {
    627         if (array != null) array[i] += value;
    628     }
    629 
    630     public int estimateResizeBuckets(long newBucketDuration) {
    631         return (int) (size() * getBucketDuration() / newBucketDuration);
    632     }
    633 
    634     /**
    635      * Utility methods for interacting with {@link DataInputStream} and
    636      * {@link DataOutputStream}, mostly dealing with writing partial arrays.
    637      */
    638     public static class DataStreamUtils {
    639         @Deprecated
    640         public static long[] readFullLongArray(DataInputStream in) throws IOException {
    641             final int size = in.readInt();
    642             if (size < 0) throw new ProtocolException("negative array size");
    643             final long[] values = new long[size];
    644             for (int i = 0; i < values.length; i++) {
    645                 values[i] = in.readLong();
    646             }
    647             return values;
    648         }
    649 
    650         /**
    651          * Read variable-length {@link Long} using protobuf-style approach.
    652          */
    653         public static long readVarLong(DataInputStream in) throws IOException {
    654             int shift = 0;
    655             long result = 0;
    656             while (shift < 64) {
    657                 byte b = in.readByte();
    658                 result |= (long) (b & 0x7F) << shift;
    659                 if ((b & 0x80) == 0)
    660                     return result;
    661                 shift += 7;
    662             }
    663             throw new ProtocolException("malformed long");
    664         }
    665 
    666         /**
    667          * Write variable-length {@link Long} using protobuf-style approach.
    668          */
    669         public static void writeVarLong(DataOutputStream out, long value) throws IOException {
    670             while (true) {
    671                 if ((value & ~0x7FL) == 0) {
    672                     out.writeByte((int) value);
    673                     return;
    674                 } else {
    675                     out.writeByte(((int) value & 0x7F) | 0x80);
    676                     value >>>= 7;
    677                 }
    678             }
    679         }
    680 
    681         public static long[] readVarLongArray(DataInputStream in) throws IOException {
    682             final int size = in.readInt();
    683             if (size == -1) return null;
    684             if (size < 0) throw new ProtocolException("negative array size");
    685             final long[] values = new long[size];
    686             for (int i = 0; i < values.length; i++) {
    687                 values[i] = readVarLong(in);
    688             }
    689             return values;
    690         }
    691 
    692         public static void writeVarLongArray(DataOutputStream out, long[] values, int size)
    693                 throws IOException {
    694             if (values == null) {
    695                 out.writeInt(-1);
    696                 return;
    697             }
    698             if (size > values.length) {
    699                 throw new IllegalArgumentException("size larger than length");
    700             }
    701             out.writeInt(size);
    702             for (int i = 0; i < size; i++) {
    703                 writeVarLong(out, values[i]);
    704             }
    705         }
    706     }
    707 
    708     /**
    709      * Utility methods for interacting with {@link Parcel} structures, mostly
    710      * dealing with writing partial arrays.
    711      */
    712     public static class ParcelUtils {
    713         public static long[] readLongArray(Parcel in) {
    714             final int size = in.readInt();
    715             if (size == -1) return null;
    716             final long[] values = new long[size];
    717             for (int i = 0; i < values.length; i++) {
    718                 values[i] = in.readLong();
    719             }
    720             return values;
    721         }
    722 
    723         public static void writeLongArray(Parcel out, long[] values, int size) {
    724             if (values == null) {
    725                 out.writeInt(-1);
    726                 return;
    727             }
    728             if (size > values.length) {
    729                 throw new IllegalArgumentException("size larger than length");
    730             }
    731             out.writeInt(size);
    732             for (int i = 0; i < size; i++) {
    733                 out.writeLong(values[i]);
    734             }
    735         }
    736     }
    737 
    738 }
    739