NetworkStatsHistory.java revision 61ee0bbb5b87fb5c4c3dc219868d52743def3d2b
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 = bucketDuration;
57        bucketStart = new long[0];
58        rx = new long[0];
59        tx = new long[0];
60        bucketCount = bucketStart.length;
61    }
62
63    public NetworkStatsHistory(Parcel in) {
64        bucketDuration = in.readLong();
65        bucketStart = readLongArray(in);
66        rx = in.createLongArray();
67        tx = in.createLongArray();
68        bucketCount = bucketStart.length;
69    }
70
71    /** {@inheritDoc} */
72    public void writeToParcel(Parcel out, int flags) {
73        out.writeLong(bucketDuration);
74        writeLongArray(out, bucketStart, bucketCount);
75        writeLongArray(out, rx, bucketCount);
76        writeLongArray(out, tx, bucketCount);
77    }
78
79    public NetworkStatsHistory(DataInputStream in) throws IOException {
80        final int version = in.readInt();
81        switch (version) {
82            case VERSION_CURRENT: {
83                bucketDuration = in.readLong();
84                bucketStart = readLongArray(in);
85                rx = readLongArray(in);
86                tx = readLongArray(in);
87                bucketCount = bucketStart.length;
88                break;
89            }
90            default: {
91                throw new ProtocolException("unexpected version: " + version);
92            }
93        }
94    }
95
96    public void writeToStream(DataOutputStream out) throws IOException {
97        out.writeInt(VERSION_CURRENT);
98        out.writeLong(bucketDuration);
99        writeLongArray(out, bucketStart, bucketCount);
100        writeLongArray(out, rx, bucketCount);
101        writeLongArray(out, tx, bucketCount);
102    }
103
104    /** {@inheritDoc} */
105    public int describeContents() {
106        return 0;
107    }
108
109    /**
110     * Record that data traffic occurred in the given time range. Will
111     * distribute across internal buckets, creating new buckets as needed.
112     */
113    public void recordData(long start, long end, long rx, long tx) {
114        // create any buckets needed by this range
115        ensureBuckets(start, end);
116
117        // distribute data usage into buckets
118        final long duration = end - start;
119        for (int i = bucketCount - 1; i >= 0; i--) {
120            final long curStart = bucketStart[i];
121            final long curEnd = curStart + bucketDuration;
122
123            // bucket is older than record; we're finished
124            if (curEnd < start) break;
125            // bucket is newer than record; keep looking
126            if (curStart > end) continue;
127
128            final long overlap = Math.min(curEnd, end) - Math.max(curStart, start);
129            if (overlap > 0) {
130                this.rx[i] += rx * overlap / duration;
131                this.tx[i] += tx * overlap / duration;
132            }
133        }
134    }
135
136    /**
137     * Ensure that buckets exist for given time range, creating as needed.
138     */
139    private void ensureBuckets(long start, long end) {
140        // normalize incoming range to bucket boundaries
141        start -= start % bucketDuration;
142        end += (bucketDuration - (end % bucketDuration)) % bucketDuration;
143
144        for (long now = start; now < end; now += bucketDuration) {
145            // try finding existing bucket
146            final int index = Arrays.binarySearch(bucketStart, 0, bucketCount, now);
147            if (index < 0) {
148                // bucket missing, create and insert
149                insertBucket(~index, now);
150            }
151        }
152    }
153
154    /**
155     * Insert new bucket at requested index and starting time.
156     */
157    private void insertBucket(int index, long start) {
158        // create more buckets when needed
159        if (bucketCount + 1 > bucketStart.length) {
160            final int newLength = bucketStart.length + 10;
161            bucketStart = Arrays.copyOf(bucketStart, newLength);
162            rx = Arrays.copyOf(rx, newLength);
163            tx = Arrays.copyOf(tx, newLength);
164        }
165
166        // create gap when inserting bucket in middle
167        if (index < bucketCount) {
168            final int dstPos = index + 1;
169            final int length = bucketCount - index;
170
171            System.arraycopy(bucketStart, index, bucketStart, dstPos, length);
172            System.arraycopy(rx, index, rx, dstPos, length);
173            System.arraycopy(tx, index, tx, dstPos, length);
174        }
175
176        bucketStart[index] = start;
177        rx[index] = 0;
178        tx[index] = 0;
179        bucketCount++;
180    }
181
182    /**
183     * Remove buckets older than requested cutoff.
184     */
185    public void removeBucketsBefore(long cutoff) {
186        int i;
187        for (i = 0; i < bucketCount; i++) {
188            final long curStart = bucketStart[i];
189            final long curEnd = curStart + bucketDuration;
190
191            // cutoff happens before or during this bucket; everything before
192            // this bucket should be removed.
193            if (curEnd > cutoff) break;
194        }
195
196        if (i > 0) {
197            final int length = bucketStart.length;
198            bucketStart = Arrays.copyOfRange(bucketStart, i, length);
199            rx = Arrays.copyOfRange(rx, i, length);
200            tx = Arrays.copyOfRange(tx, i, length);
201            bucketCount -= i;
202        }
203    }
204
205    /**
206     * @deprecated only for temporary testing
207     */
208    @Deprecated
209    public void generateRandom(long start, long end, long rx, long tx) {
210        ensureBuckets(start, end);
211
212        final Random r = new Random();
213        while (rx > 1024 && tx > 1024) {
214            final long curStart = randomLong(r, start, end);
215            final long curEnd = randomLong(r, curStart, end);
216            final long curRx = randomLong(r, 0, rx);
217            final long curTx = randomLong(r, 0, tx);
218
219            recordData(curStart, curEnd, curRx, curTx);
220
221            rx -= curRx;
222            tx -= curTx;
223        }
224    }
225
226    private static long randomLong(Random r, long start, long end) {
227        return (long) (start + (r.nextFloat() * (end - start)));
228    }
229
230    public void dump(String prefix, PrintWriter pw) {
231        pw.print(prefix);
232        pw.print("NetworkStatsHistory: bucketDuration="); pw.println(bucketDuration);
233        for (int i = 0; i < bucketCount; i++) {
234            pw.print(prefix);
235            pw.print("  bucketStart="); pw.print(bucketStart[i]);
236            pw.print(" rx="); pw.print(rx[i]);
237            pw.print(" tx="); pw.println(tx[i]);
238        }
239    }
240
241    @Override
242    public String toString() {
243        final CharArrayWriter writer = new CharArrayWriter();
244        dump("", new PrintWriter(writer));
245        return writer.toString();
246    }
247
248    public static final Creator<NetworkStatsHistory> CREATOR = new Creator<NetworkStatsHistory>() {
249        public NetworkStatsHistory createFromParcel(Parcel in) {
250            return new NetworkStatsHistory(in);
251        }
252
253        public NetworkStatsHistory[] newArray(int size) {
254            return new NetworkStatsHistory[size];
255        }
256    };
257
258    private static long[] readLongArray(DataInputStream in) throws IOException {
259        final int size = in.readInt();
260        final long[] values = new long[size];
261        for (int i = 0; i < values.length; i++) {
262            values[i] = in.readLong();
263        }
264        return values;
265    }
266
267    private static void writeLongArray(DataOutputStream out, long[] values, int size) throws IOException {
268        if (size > values.length) {
269            throw new IllegalArgumentException("size larger than length");
270        }
271        out.writeInt(size);
272        for (int i = 0; i < size; i++) {
273            out.writeLong(values[i]);
274        }
275    }
276
277    private static long[] readLongArray(Parcel in) {
278        final int size = in.readInt();
279        final long[] values = new long[size];
280        for (int i = 0; i < values.length; i++) {
281            values[i] = in.readLong();
282        }
283        return values;
284    }
285
286    private static void writeLongArray(Parcel out, long[] values, int size) {
287        if (size > values.length) {
288            throw new IllegalArgumentException("size larger than length");
289        }
290        out.writeInt(size);
291        for (int i = 0; i < size; i++) {
292            out.writeLong(values[i]);
293        }
294    }
295
296}
297