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