NetworkStatsHistory.java revision 4a97122ebf4d92a3f94402041729d77905e6c0c0
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 android.os.Parcel;
20import android.os.Parcelable;
21
22import java.io.CharArrayWriter;
23import java.io.DataInputStream;
24import java.io.DataOutputStream;
25import java.io.IOException;
26import java.io.PrintWriter;
27import java.net.ProtocolException;
28import java.util.Arrays;
29import java.util.Random;
30
31/**
32 * Collection of historical network statistics, recorded into equally-sized
33 * "buckets" in time. Internally it stores data in {@code long} series for more
34 * efficient persistence.
35 * <p>
36 * Each bucket is defined by a {@link #bucketStart} timestamp, and lasts for
37 * {@link #bucketDuration}. Internally assumes that {@link #bucketStart} is
38 * sorted at all times.
39 *
40 * @hide
41 */
42public class NetworkStatsHistory implements Parcelable {
43    private static final int VERSION_CURRENT = 1;
44
45    // TODO: teach about zigzag encoding to use less disk space
46    // TODO: teach how to convert between bucket sizes
47
48    public final long bucketDuration;
49
50    public int bucketCount;
51    public long[] bucketStart;
52    public long[] rx;
53    public long[] tx;
54
55    public NetworkStatsHistory(long bucketDuration) {
56        this(bucketDuration, 10);
57    }
58
59    public NetworkStatsHistory(long bucketDuration, int initialSize) {
60        this.bucketDuration = bucketDuration;
61        bucketStart = new long[initialSize];
62        rx = new long[initialSize];
63        tx = new long[initialSize];
64        bucketCount = 0;
65    }
66
67    public NetworkStatsHistory(Parcel in) {
68        bucketDuration = in.readLong();
69        bucketStart = readLongArray(in);
70        rx = in.createLongArray();
71        tx = in.createLongArray();
72        bucketCount = bucketStart.length;
73    }
74
75    /** {@inheritDoc} */
76    public void writeToParcel(Parcel out, int flags) {
77        out.writeLong(bucketDuration);
78        writeLongArray(out, bucketStart, bucketCount);
79        writeLongArray(out, rx, bucketCount);
80        writeLongArray(out, tx, bucketCount);
81    }
82
83    public NetworkStatsHistory(DataInputStream in) throws IOException {
84        final int version = in.readInt();
85        switch (version) {
86            case VERSION_CURRENT: {
87                bucketDuration = in.readLong();
88                bucketStart = readLongArray(in);
89                rx = readLongArray(in);
90                tx = readLongArray(in);
91                bucketCount = bucketStart.length;
92                break;
93            }
94            default: {
95                throw new ProtocolException("unexpected version: " + version);
96            }
97        }
98    }
99
100    public void writeToStream(DataOutputStream out) throws IOException {
101        out.writeInt(VERSION_CURRENT);
102        out.writeLong(bucketDuration);
103        writeLongArray(out, bucketStart, bucketCount);
104        writeLongArray(out, rx, bucketCount);
105        writeLongArray(out, tx, bucketCount);
106    }
107
108    /** {@inheritDoc} */
109    public int describeContents() {
110        return 0;
111    }
112
113    /**
114     * Record that data traffic occurred in the given time range. Will
115     * distribute across internal buckets, creating new buckets as needed.
116     */
117    public void recordData(long start, long end, long rx, long tx) {
118        // create any buckets needed by this range
119        ensureBuckets(start, end);
120
121        // distribute data usage into buckets
122        final long duration = end - start;
123        for (int i = bucketCount - 1; i >= 0; i--) {
124            final long curStart = bucketStart[i];
125            final long curEnd = curStart + bucketDuration;
126
127            // bucket is older than record; we're finished
128            if (curEnd < start) break;
129            // bucket is newer than record; keep looking
130            if (curStart > end) continue;
131
132            final long overlap = Math.min(curEnd, end) - Math.max(curStart, start);
133            if (overlap > 0) {
134                this.rx[i] += rx * overlap / duration;
135                this.tx[i] += tx * overlap / duration;
136            }
137        }
138    }
139
140    /**
141     * Record an entire {@link NetworkStatsHistory} into this history. Usually
142     * for combining together stats for external reporting.
143     */
144    public void recordEntireHistory(NetworkStatsHistory input) {
145        for (int i = 0; i < input.bucketCount; i++) {
146            final long start = input.bucketStart[i];
147            final long end = start + input.bucketDuration;
148            recordData(start, end, input.rx[i], input.tx[i]);
149        }
150    }
151
152    /**
153     * Ensure that buckets exist for given time range, creating as needed.
154     */
155    private void ensureBuckets(long start, long end) {
156        // normalize incoming range to bucket boundaries
157        start -= start % bucketDuration;
158        end += (bucketDuration - (end % bucketDuration)) % bucketDuration;
159
160        for (long now = start; now < end; now += bucketDuration) {
161            // try finding existing bucket
162            final int index = Arrays.binarySearch(bucketStart, 0, bucketCount, now);
163            if (index < 0) {
164                // bucket missing, create and insert
165                insertBucket(~index, now);
166            }
167        }
168    }
169
170    /**
171     * Insert new bucket at requested index and starting time.
172     */
173    private void insertBucket(int index, long start) {
174        // create more buckets when needed
175        if (bucketCount >= bucketStart.length) {
176            final int newLength = Math.max(bucketStart.length, 10) * 3 / 2;
177            bucketStart = Arrays.copyOf(bucketStart, newLength);
178            rx = Arrays.copyOf(rx, newLength);
179            tx = Arrays.copyOf(tx, newLength);
180        }
181
182        // create gap when inserting bucket in middle
183        if (index < bucketCount) {
184            final int dstPos = index + 1;
185            final int length = bucketCount - index;
186
187            System.arraycopy(bucketStart, index, bucketStart, dstPos, length);
188            System.arraycopy(rx, index, rx, dstPos, length);
189            System.arraycopy(tx, index, tx, dstPos, length);
190        }
191
192        bucketStart[index] = start;
193        rx[index] = 0;
194        tx[index] = 0;
195        bucketCount++;
196    }
197
198    /**
199     * Remove buckets older than requested cutoff.
200     */
201    public void removeBucketsBefore(long cutoff) {
202        int i;
203        for (i = 0; i < bucketCount; i++) {
204            final long curStart = bucketStart[i];
205            final long curEnd = curStart + bucketDuration;
206
207            // cutoff happens before or during this bucket; everything before
208            // this bucket should be removed.
209            if (curEnd > cutoff) break;
210        }
211
212        if (i > 0) {
213            final int length = bucketStart.length;
214            bucketStart = Arrays.copyOfRange(bucketStart, i, length);
215            rx = Arrays.copyOfRange(rx, i, length);
216            tx = Arrays.copyOfRange(tx, i, length);
217            bucketCount -= i;
218        }
219    }
220
221    /**
222     * Return interpolated data usage across the requested range. Interpolates
223     * across buckets, so values may be rounded slightly.
224     */
225    public long[] getTotalData(long start, long end, long[] outTotal) {
226        long rx = 0;
227        long tx = 0;
228
229        for (int i = bucketCount - 1; i >= 0; i--) {
230            final long curStart = bucketStart[i];
231            final long curEnd = curStart + bucketDuration;
232
233            // bucket is older than record; we're finished
234            if (curEnd < start) break;
235            // bucket is newer than record; keep looking
236            if (curStart > end) continue;
237
238            final long overlap = Math.min(curEnd, end) - Math.max(curStart, start);
239            if (overlap > 0) {
240                rx += this.rx[i] * overlap / bucketDuration;
241                tx += this.tx[i] * overlap / bucketDuration;
242            }
243        }
244
245        if (outTotal == null || outTotal.length != 2) {
246            outTotal = new long[2];
247        }
248        outTotal[0] = rx;
249        outTotal[1] = tx;
250        return outTotal;
251    }
252
253    /**
254     * @deprecated only for temporary testing
255     */
256    @Deprecated
257    public void generateRandom(long start, long end, long rx, long tx) {
258        ensureBuckets(start, end);
259
260        final Random r = new Random();
261        while (rx > 1024 && tx > 1024) {
262            final long curStart = randomLong(r, start, end);
263            final long curEnd = randomLong(r, curStart, end);
264            final long curRx = randomLong(r, 0, rx);
265            final long curTx = randomLong(r, 0, tx);
266
267            recordData(curStart, curEnd, curRx, curTx);
268
269            rx -= curRx;
270            tx -= curTx;
271        }
272    }
273
274    private static long randomLong(Random r, long start, long end) {
275        return (long) (start + (r.nextFloat() * (end - start)));
276    }
277
278    public void dump(String prefix, PrintWriter pw) {
279        pw.print(prefix);
280        pw.print("NetworkStatsHistory: bucketDuration="); pw.println(bucketDuration);
281        for (int i = 0; i < bucketCount; i++) {
282            pw.print(prefix);
283            pw.print("  bucketStart="); pw.print(bucketStart[i]);
284            pw.print(" rx="); pw.print(rx[i]);
285            pw.print(" tx="); pw.println(tx[i]);
286        }
287    }
288
289    @Override
290    public String toString() {
291        final CharArrayWriter writer = new CharArrayWriter();
292        dump("", new PrintWriter(writer));
293        return writer.toString();
294    }
295
296    public static final Creator<NetworkStatsHistory> CREATOR = new Creator<NetworkStatsHistory>() {
297        public NetworkStatsHistory createFromParcel(Parcel in) {
298            return new NetworkStatsHistory(in);
299        }
300
301        public NetworkStatsHistory[] newArray(int size) {
302            return new NetworkStatsHistory[size];
303        }
304    };
305
306    private static long[] readLongArray(DataInputStream in) throws IOException {
307        final int size = in.readInt();
308        final long[] values = new long[size];
309        for (int i = 0; i < values.length; i++) {
310            values[i] = in.readLong();
311        }
312        return values;
313    }
314
315    private static void writeLongArray(DataOutputStream out, long[] values, int size) throws IOException {
316        if (size > values.length) {
317            throw new IllegalArgumentException("size larger than length");
318        }
319        out.writeInt(size);
320        for (int i = 0; i < size; i++) {
321            out.writeLong(values[i]);
322        }
323    }
324
325    private static long[] readLongArray(Parcel in) {
326        final int size = in.readInt();
327        final long[] values = new long[size];
328        for (int i = 0; i < values.length; i++) {
329            values[i] = in.readLong();
330        }
331        return values;
332    }
333
334    private static void writeLongArray(Parcel out, long[] values, int size) {
335        if (size > values.length) {
336            throw new IllegalArgumentException("size larger than length");
337        }
338        out.writeInt(size);
339        for (int i = 0; i < size; i++) {
340            out.writeLong(values[i]);
341        }
342    }
343
344}
345