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