NetworkStats.java revision d37948f6ed1667d077e0e3a38808f42f981ddcc2
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;
21import android.os.SystemClock;
22import android.util.SparseBooleanArray;
23
24import java.io.CharArrayWriter;
25import java.io.PrintWriter;
26import java.util.Arrays;
27import java.util.HashSet;
28
29/**
30 * Collection of active network statistics. Can contain summary details across
31 * all interfaces, or details with per-UID granularity. Internally stores data
32 * as a large table, closely matching {@code /proc/} data format. This structure
33 * optimizes for rapid in-memory comparison, but consider using
34 * {@link NetworkStatsHistory} when persisting.
35 *
36 * @hide
37 */
38public class NetworkStats implements Parcelable {
39    /** {@link #iface} value when interface details unavailable. */
40    public static final String IFACE_ALL = null;
41    /** {@link #uid} value when UID details unavailable. */
42    public static final int UID_ALL = -1;
43    /** {@link #tag} value for without tag. */
44    public static final int TAG_NONE = 0;
45
46    /**
47     * {@link SystemClock#elapsedRealtime()} timestamp when this data was
48     * generated.
49     */
50    private final long elapsedRealtime;
51    private int size;
52    private String[] iface;
53    private int[] uid;
54    private int[] tag;
55    private long[] rxBytes;
56    private long[] rxPackets;
57    private long[] txBytes;
58    private long[] txPackets;
59
60    public static class Entry {
61        public String iface;
62        public int uid;
63        public int tag;
64        public long rxBytes;
65        public long rxPackets;
66        public long txBytes;
67        public long txPackets;
68
69        public Entry() {
70        }
71
72        public Entry(String iface, int uid, int tag, long rxBytes, long rxPackets, long txBytes,
73                long txPackets) {
74            this.iface = iface;
75            this.uid = uid;
76            this.tag = tag;
77            this.rxBytes = rxBytes;
78            this.rxPackets = rxPackets;
79            this.txBytes = txBytes;
80            this.txPackets = txPackets;
81        }
82    }
83
84    public NetworkStats(long elapsedRealtime, int initialSize) {
85        this.elapsedRealtime = elapsedRealtime;
86        this.size = 0;
87        this.iface = new String[initialSize];
88        this.uid = new int[initialSize];
89        this.tag = new int[initialSize];
90        this.rxBytes = new long[initialSize];
91        this.rxPackets = new long[initialSize];
92        this.txBytes = new long[initialSize];
93        this.txPackets = new long[initialSize];
94    }
95
96    public NetworkStats(Parcel parcel) {
97        elapsedRealtime = parcel.readLong();
98        size = parcel.readInt();
99        iface = parcel.createStringArray();
100        uid = parcel.createIntArray();
101        tag = parcel.createIntArray();
102        rxBytes = parcel.createLongArray();
103        rxPackets = parcel.createLongArray();
104        txBytes = parcel.createLongArray();
105        txPackets = parcel.createLongArray();
106    }
107
108    public NetworkStats addValues(String iface, int uid, int tag, long rxBytes, long rxPackets,
109            long txBytes, long txPackets) {
110        return addValues(new Entry(iface, uid, tag, rxBytes, rxPackets, txBytes, txPackets));
111    }
112
113    /**
114     * Add new stats entry, copying from given {@link Entry}. The {@link Entry}
115     * object can be recycled across multiple calls.
116     */
117    public NetworkStats addValues(Entry entry) {
118        if (size >= this.iface.length) {
119            final int newLength = Math.max(iface.length, 10) * 3 / 2;
120            iface = Arrays.copyOf(iface, newLength);
121            uid = Arrays.copyOf(uid, newLength);
122            tag = Arrays.copyOf(tag, newLength);
123            rxBytes = Arrays.copyOf(rxBytes, newLength);
124            rxPackets = Arrays.copyOf(rxPackets, newLength);
125            txBytes = Arrays.copyOf(txBytes, newLength);
126            txPackets = Arrays.copyOf(txPackets, newLength);
127        }
128
129        iface[size] = entry.iface;
130        uid[size] = entry.uid;
131        tag[size] = entry.tag;
132        rxBytes[size] = entry.rxBytes;
133        rxPackets[size] = entry.rxPackets;
134        txBytes[size] = entry.txBytes;
135        txPackets[size] = entry.txPackets;
136        size++;
137
138        return this;
139    }
140
141    /**
142     * Return specific stats entry.
143     */
144    public Entry getValues(int i, Entry recycle) {
145        final Entry entry = recycle != null ? recycle : new Entry();
146        entry.iface = iface[i];
147        entry.uid = uid[i];
148        entry.tag = tag[i];
149        entry.rxBytes = rxBytes[i];
150        entry.rxPackets = rxPackets[i];
151        entry.txBytes = txBytes[i];
152        entry.txPackets = txPackets[i];
153        return entry;
154    }
155
156    public long getElapsedRealtime() {
157        return elapsedRealtime;
158    }
159
160    public int size() {
161        return size;
162    }
163
164    // @VisibleForTesting
165    public int internalSize() {
166        return iface.length;
167    }
168
169    public NetworkStats combineValues(String iface, int uid, int tag, long rxBytes, long rxPackets,
170            long txBytes, long txPackets) {
171        return combineValues(new Entry(iface, uid, tag, rxBytes, rxPackets, txBytes, txPackets));
172    }
173
174    /**
175     * Combine given values with an existing row, or create a new row if
176     * {@link #findIndex(String, int, int)} is unable to find match. Can also be
177     * used to subtract values from existing rows.
178     */
179    public NetworkStats combineValues(Entry entry) {
180        final int i = findIndex(entry.iface, entry.uid, entry.tag);
181        if (i == -1) {
182            // only create new entry when positive contribution
183            addValues(entry);
184        } else {
185            rxBytes[i] += entry.rxBytes;
186            rxPackets[i] += entry.rxPackets;
187            txBytes[i] += entry.txBytes;
188            txPackets[i] += entry.txPackets;
189        }
190        return this;
191    }
192
193    /**
194     * Find first stats index that matches the requested parameters.
195     */
196    public int findIndex(String iface, int uid, int tag) {
197        for (int i = 0; i < size; i++) {
198            if (equal(iface, this.iface[i]) && uid == this.uid[i] && tag == this.tag[i]) {
199                return i;
200            }
201        }
202        return -1;
203    }
204
205    /**
206     * Return list of unique interfaces known by this data structure.
207     */
208    public String[] getUniqueIfaces() {
209        final HashSet<String> ifaces = new HashSet<String>();
210        for (String iface : this.iface) {
211            if (iface != IFACE_ALL) {
212                ifaces.add(iface);
213            }
214        }
215        return ifaces.toArray(new String[ifaces.size()]);
216    }
217
218    /**
219     * Return list of unique UIDs known by this data structure.
220     */
221    public int[] getUniqueUids() {
222        final SparseBooleanArray uids = new SparseBooleanArray();
223        for (int uid : this.uid) {
224            uids.put(uid, true);
225        }
226
227        final int size = uids.size();
228        final int[] result = new int[size];
229        for (int i = 0; i < size; i++) {
230            result[i] = uids.keyAt(i);
231        }
232        return result;
233    }
234
235    /**
236     * Subtract the given {@link NetworkStats}, effectively leaving the delta
237     * between two snapshots in time. Assumes that statistics rows collect over
238     * time, and that none of them have disappeared.
239     *
240     * @throws IllegalArgumentException when given {@link NetworkStats} is
241     *             non-monotonic.
242     */
243    public NetworkStats subtract(NetworkStats value) {
244        return subtract(value, true, false);
245    }
246
247    /**
248     * Subtract the given {@link NetworkStats}, effectively leaving the delta
249     * between two snapshots in time. Assumes that statistics rows collect over
250     * time, and that none of them have disappeared.
251     * <p>
252     * Instead of throwing when counters are non-monotonic, this variant clamps
253     * results to never be negative.
254     */
255    public NetworkStats subtractClamped(NetworkStats value) {
256        return subtract(value, false, true);
257    }
258
259    /**
260     * Subtract the given {@link NetworkStats}, effectively leaving the delta
261     * between two snapshots in time. Assumes that statistics rows collect over
262     * time, and that none of them have disappeared.
263     *
264     * @param enforceMonotonic Validate that incoming value is strictly
265     *            monotonic compared to this object.
266     * @param clampNegative Instead of throwing like {@code enforceMonotonic},
267     *            clamp resulting counters at 0 to prevent negative values.
268     */
269    private NetworkStats subtract(
270            NetworkStats value, boolean enforceMonotonic, boolean clampNegative) {
271        final long deltaRealtime = this.elapsedRealtime - value.elapsedRealtime;
272        if (enforceMonotonic && deltaRealtime < 0) {
273            throw new IllegalArgumentException("found non-monotonic realtime");
274        }
275
276        // result will have our rows, and elapsed time between snapshots
277        final Entry entry = new Entry();
278        final NetworkStats result = new NetworkStats(deltaRealtime, size);
279        for (int i = 0; i < size; i++) {
280            entry.iface = iface[i];
281            entry.uid = uid[i];
282            entry.tag = tag[i];
283
284            // find remote row that matches, and subtract
285            final int j = value.findIndex(entry.iface, entry.uid, entry.tag);
286            if (j == -1) {
287                // newly appearing row, return entire value
288                entry.rxBytes = rxBytes[i];
289                entry.rxPackets = rxPackets[i];
290                entry.txBytes = txBytes[i];
291                entry.txPackets = txPackets[i];
292            } else {
293                // existing row, subtract remote value
294                entry.rxBytes = rxBytes[i] - value.rxBytes[j];
295                entry.rxPackets = rxPackets[i] - value.rxPackets[j];
296                entry.txBytes = txBytes[i] - value.txBytes[j];
297                entry.txPackets = txPackets[i] - value.txPackets[j];
298                if (enforceMonotonic
299                        && (entry.rxBytes < 0 || entry.rxPackets < 0 || entry.txBytes < 0
300                                || entry.txPackets < 0)) {
301                    throw new IllegalArgumentException("found non-monotonic values");
302                }
303                if (clampNegative) {
304                    entry.rxBytes = Math.max(0, entry.rxBytes);
305                    entry.rxPackets = Math.max(0, entry.rxPackets);
306                    entry.txBytes = Math.max(0, entry.txBytes);
307                    entry.txPackets = Math.max(0, entry.txPackets);
308                }
309            }
310
311            result.addValues(entry);
312        }
313
314        return result;
315    }
316
317    private static boolean equal(Object a, Object b) {
318        return a == b || (a != null && a.equals(b));
319    }
320
321    public void dump(String prefix, PrintWriter pw) {
322        pw.print(prefix);
323        pw.print("NetworkStats: elapsedRealtime="); pw.println(elapsedRealtime);
324        for (int i = 0; i < size; i++) {
325            pw.print(prefix);
326            pw.print("  iface="); pw.print(iface[i]);
327            pw.print(" uid="); pw.print(uid[i]);
328            pw.print(" tag="); pw.print(tag[i]);
329            pw.print(" rxBytes="); pw.print(rxBytes[i]);
330            pw.print(" rxPackets="); pw.print(rxPackets[i]);
331            pw.print(" txBytes="); pw.print(txBytes[i]);
332            pw.print(" txPackets="); pw.println(txPackets[i]);
333        }
334    }
335
336    @Override
337    public String toString() {
338        final CharArrayWriter writer = new CharArrayWriter();
339        dump("", new PrintWriter(writer));
340        return writer.toString();
341    }
342
343    /** {@inheritDoc} */
344    public int describeContents() {
345        return 0;
346    }
347
348    /** {@inheritDoc} */
349    public void writeToParcel(Parcel dest, int flags) {
350        dest.writeLong(elapsedRealtime);
351        dest.writeInt(size);
352        dest.writeStringArray(iface);
353        dest.writeIntArray(uid);
354        dest.writeIntArray(tag);
355        dest.writeLongArray(rxBytes);
356        dest.writeLongArray(rxPackets);
357        dest.writeLongArray(txBytes);
358        dest.writeLongArray(txPackets);
359    }
360
361    public static final Creator<NetworkStats> CREATOR = new Creator<NetworkStats>() {
362        public NetworkStats createFromParcel(Parcel in) {
363            return new NetworkStats(in);
364        }
365
366        public NetworkStats[] newArray(int size) {
367            return new NetworkStats[size];
368        }
369    };
370}
371