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