NetworkStatsHistory.java revision 75279904202357565cf5a1cb11148d01f42b4569
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.util.Arrays;
28
29/**
30 * Collection of historical network statistics, recorded into equally-sized
31 * "buckets" in time. Internally it stores data in {@code long} series for more
32 * efficient persistence.
33 * <p>
34 * Each bucket is defined by a {@link #bucketStart} timestamp, and lasts for
35 * {@link #bucketDuration}. Internally assumes that {@link #bucketStart} is
36 * sorted at all times.
37 *
38 * @hide
39 */
40public class NetworkStatsHistory implements Parcelable {
41    private static final int VERSION = 1;
42
43    /** {@link #uid} value when UID details unavailable. */
44    public static final int UID_ALL = -1;
45
46    // TODO: teach about zigzag encoding to use less disk space
47    // TODO: teach how to convert between bucket sizes
48
49    public final int networkType;
50    public final String identity;
51    public final int uid;
52    public final long bucketDuration;
53
54    int bucketCount;
55    long[] bucketStart;
56    long[] rx;
57    long[] tx;
58
59    public NetworkStatsHistory(int networkType, String identity, int uid, long bucketDuration) {
60        this.networkType = networkType;
61        this.identity = identity;
62        this.uid = uid;
63        this.bucketDuration = bucketDuration;
64        bucketStart = new long[0];
65        rx = new long[0];
66        tx = new long[0];
67        bucketCount = bucketStart.length;
68    }
69
70    public NetworkStatsHistory(Parcel in) {
71        networkType = in.readInt();
72        identity = in.readString();
73        uid = in.readInt();
74        bucketDuration = in.readLong();
75        bucketStart = readLongArray(in);
76        rx = in.createLongArray();
77        tx = in.createLongArray();
78        bucketCount = bucketStart.length;
79    }
80
81    /** {@inheritDoc} */
82    public void writeToParcel(Parcel out, int flags) {
83        out.writeInt(networkType);
84        out.writeString(identity);
85        out.writeInt(uid);
86        out.writeLong(bucketDuration);
87        writeLongArray(out, bucketStart, bucketCount);
88        writeLongArray(out, rx, bucketCount);
89        writeLongArray(out, tx, bucketCount);
90    }
91
92    public NetworkStatsHistory(DataInputStream in) throws IOException {
93        final int version = in.readInt();
94        networkType = in.readInt();
95        identity = in.readUTF();
96        uid = in.readInt();
97        bucketDuration = in.readLong();
98        bucketStart = readLongArray(in);
99        rx = readLongArray(in);
100        tx = readLongArray(in);
101        bucketCount = bucketStart.length;
102    }
103
104    public void writeToStream(DataOutputStream out) throws IOException {
105        out.writeInt(VERSION);
106        out.writeInt(networkType);
107        out.writeUTF(identity);
108        out.writeInt(uid);
109        out.writeLong(bucketDuration);
110        writeLongArray(out, bucketStart, bucketCount);
111        writeLongArray(out, rx, bucketCount);
112        writeLongArray(out, tx, bucketCount);
113    }
114
115    /** {@inheritDoc} */
116    public int describeContents() {
117        return 0;
118    }
119
120    /**
121     * Record that data traffic occurred in the given time range. Will
122     * distribute across internal buckets, creating new buckets as needed.
123     */
124    public void recordData(long start, long end, long rx, long tx) {
125        // create any buckets needed by this range
126        ensureBuckets(start, end);
127
128        // distribute data usage into buckets
129        final long duration = end - start;
130        for (int i = bucketCount - 1; i >= 0; i--) {
131            final long curStart = bucketStart[i];
132            final long curEnd = curStart + bucketDuration;
133
134            // bucket is older than record; we're finished
135            if (curEnd < start) break;
136            // bucket is newer than record; keep looking
137            if (curStart > end) continue;
138
139            final long overlap = Math.min(curEnd, end) - Math.max(curStart, start);
140            if (overlap > 0) {
141                this.rx[i] += rx * overlap / duration;
142                this.tx[i] += tx * overlap / duration;
143            }
144        }
145    }
146
147    /**
148     * Ensure that buckets exist for given time range, creating as needed.
149     */
150    private void ensureBuckets(long start, long end) {
151        // normalize incoming range to bucket boundaries
152        start -= start % bucketDuration;
153        end += (bucketDuration - (end % bucketDuration)) % bucketDuration;
154
155        for (long now = start; now < end; now += bucketDuration) {
156            // try finding existing bucket
157            final int index = Arrays.binarySearch(bucketStart, 0, bucketCount, now);
158            if (index < 0) {
159                // bucket missing, create and insert
160                insertBucket(~index, now);
161            }
162        }
163    }
164
165    /**
166     * Insert new bucket at requested index and starting time.
167     */
168    private void insertBucket(int index, long start) {
169        // create more buckets when needed
170        if (bucketCount + 1 > bucketStart.length) {
171            final int newLength = bucketStart.length + 10;
172            bucketStart = Arrays.copyOf(bucketStart, newLength);
173            rx = Arrays.copyOf(rx, newLength);
174            tx = Arrays.copyOf(tx, newLength);
175        }
176
177        // create gap when inserting bucket in middle
178        if (index < bucketCount) {
179            final int dstPos = index + 1;
180            final int length = bucketCount - index;
181
182            System.arraycopy(bucketStart, index, bucketStart, dstPos, length);
183            System.arraycopy(rx, index, rx, dstPos, length);
184            System.arraycopy(tx, index, tx, dstPos, length);
185        }
186
187        bucketStart[index] = start;
188        rx[index] = 0;
189        tx[index] = 0;
190        bucketCount++;
191    }
192
193    /**
194     * Remove buckets older than requested cutoff.
195     */
196    public void removeBucketsBefore(long cutoff) {
197        int i;
198        for (i = 0; i < bucketCount; i++) {
199            final long curStart = bucketStart[i];
200            final long curEnd = curStart + bucketDuration;
201
202            // cutoff happens before or during this bucket; everything before
203            // this bucket should be removed.
204            if (curEnd > cutoff) break;
205        }
206
207        if (i > 0) {
208            final int length = bucketStart.length;
209            bucketStart = Arrays.copyOfRange(bucketStart, i, length);
210            rx = Arrays.copyOfRange(rx, i, length);
211            tx = Arrays.copyOfRange(tx, i, length);
212            bucketCount -= i;
213        }
214    }
215
216    public void dump(String prefix, PrintWriter pw) {
217        // TODO: consider stripping identity when dumping
218        pw.print(prefix);
219        pw.print("NetworkStatsHistory: networkType="); pw.print(networkType);
220        pw.print(" identity="); pw.print(identity);
221        pw.print(" uid="); pw.println(uid);
222        for (int i = 0; i < bucketCount; i++) {
223            pw.print(prefix);
224            pw.print("  timestamp="); pw.print(bucketStart[i]);
225            pw.print(" rx="); pw.print(rx[i]);
226            pw.print(" tx="); pw.println(tx[i]);
227        }
228    }
229
230    @Override
231    public String toString() {
232        final CharArrayWriter writer = new CharArrayWriter();
233        dump("", new PrintWriter(writer));
234        return writer.toString();
235    }
236
237    public static final Creator<NetworkStatsHistory> CREATOR = new Creator<NetworkStatsHistory>() {
238        public NetworkStatsHistory createFromParcel(Parcel in) {
239            return new NetworkStatsHistory(in);
240        }
241
242        public NetworkStatsHistory[] newArray(int size) {
243            return new NetworkStatsHistory[size];
244        }
245    };
246
247    private static long[] readLongArray(DataInputStream in) throws IOException {
248        final int size = in.readInt();
249        final long[] values = new long[size];
250        for (int i = 0; i < values.length; i++) {
251            values[i] = in.readLong();
252        }
253        return values;
254    }
255
256    private static void writeLongArray(DataOutputStream out, long[] values, int size) throws IOException {
257        if (size > values.length) {
258            throw new IllegalArgumentException("size larger than length");
259        }
260        out.writeInt(size);
261        for (int i = 0; i < size; i++) {
262            out.writeLong(values[i]);
263        }
264    }
265
266    private static long[] readLongArray(Parcel in) {
267        final int size = in.readInt();
268        final long[] values = new long[size];
269        for (int i = 0; i < values.length; i++) {
270            values[i] = in.readLong();
271        }
272        return values;
273    }
274
275    private static void writeLongArray(Parcel out, long[] values, int size) {
276        if (size > values.length) {
277            throw new IllegalArgumentException("size larger than length");
278        }
279        out.writeInt(size);
280        for (int i = 0; i < size; i++) {
281            out.writeLong(values[i]);
282        }
283    }
284
285}
286