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