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