NetworkStats.java revision 07b0dd9a092273f0451cd9881312cb9b22a7af3f
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.Log;
23import android.util.SparseBooleanArray;
24
25import com.android.internal.util.Objects;
26
27import java.io.CharArrayWriter;
28import java.io.PrintWriter;
29import java.util.Arrays;
30import java.util.HashSet;
31
32/**
33 * Collection of active network statistics. Can contain summary details across
34 * all interfaces, or details with per-UID granularity. Internally stores data
35 * as a large table, closely matching {@code /proc/} data format. This structure
36 * optimizes for rapid in-memory comparison, but consider using
37 * {@link NetworkStatsHistory} when persisting.
38 *
39 * @hide
40 */
41public class NetworkStats implements Parcelable {
42    private static final String TAG = "NetworkStats";
43
44    /** {@link #iface} value when interface details unavailable. */
45    public static final String IFACE_ALL = null;
46    /** {@link #uid} value when UID details unavailable. */
47    public static final int UID_ALL = -1;
48    /** {@link #set} value when all sets combined. */
49    public static final int SET_ALL = -1;
50    /** {@link #set} value where background data is accounted. */
51    public static final int SET_DEFAULT = 0;
52    /** {@link #set} value where foreground data is accounted. */
53    public static final int SET_FOREGROUND = 1;
54    /** {@link #tag} value for total data across all tags. */
55    public static final int TAG_NONE = 0;
56
57    /**
58     * {@link SystemClock#elapsedRealtime()} timestamp when this data was
59     * generated.
60     */
61    private final long elapsedRealtime;
62    private int size;
63    private String[] iface;
64    private int[] uid;
65    private int[] set;
66    private int[] tag;
67    private long[] rxBytes;
68    private long[] rxPackets;
69    private long[] txBytes;
70    private long[] txPackets;
71    private long[] operations;
72
73    public static class Entry {
74        public String iface;
75        public int uid;
76        public int set;
77        public int tag;
78        public long rxBytes;
79        public long rxPackets;
80        public long txBytes;
81        public long txPackets;
82        public long operations;
83
84        public Entry() {
85            this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
86        }
87
88        public Entry(long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) {
89            this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets,
90                    operations);
91        }
92
93        public Entry(String iface, int uid, int set, int tag, long rxBytes, long rxPackets,
94                long txBytes, long txPackets, long operations) {
95            this.iface = iface;
96            this.uid = uid;
97            this.set = set;
98            this.tag = tag;
99            this.rxBytes = rxBytes;
100            this.rxPackets = rxPackets;
101            this.txBytes = txBytes;
102            this.txPackets = txPackets;
103            this.operations = operations;
104        }
105    }
106
107    public NetworkStats(long elapsedRealtime, int initialSize) {
108        this.elapsedRealtime = elapsedRealtime;
109        this.size = 0;
110        this.iface = new String[initialSize];
111        this.uid = new int[initialSize];
112        this.set = new int[initialSize];
113        this.tag = new int[initialSize];
114        this.rxBytes = new long[initialSize];
115        this.rxPackets = new long[initialSize];
116        this.txBytes = new long[initialSize];
117        this.txPackets = new long[initialSize];
118        this.operations = new long[initialSize];
119    }
120
121    public NetworkStats(Parcel parcel) {
122        elapsedRealtime = parcel.readLong();
123        size = parcel.readInt();
124        iface = parcel.createStringArray();
125        uid = parcel.createIntArray();
126        set = parcel.createIntArray();
127        tag = parcel.createIntArray();
128        rxBytes = parcel.createLongArray();
129        rxPackets = parcel.createLongArray();
130        txBytes = parcel.createLongArray();
131        txPackets = parcel.createLongArray();
132        operations = parcel.createLongArray();
133    }
134
135    /** {@inheritDoc} */
136    public void writeToParcel(Parcel dest, int flags) {
137        dest.writeLong(elapsedRealtime);
138        dest.writeInt(size);
139        dest.writeStringArray(iface);
140        dest.writeIntArray(uid);
141        dest.writeIntArray(set);
142        dest.writeIntArray(tag);
143        dest.writeLongArray(rxBytes);
144        dest.writeLongArray(rxPackets);
145        dest.writeLongArray(txBytes);
146        dest.writeLongArray(txPackets);
147        dest.writeLongArray(operations);
148    }
149
150    // @VisibleForTesting
151    public NetworkStats addIfaceValues(
152            String iface, long rxBytes, long rxPackets, long txBytes, long txPackets) {
153        return addValues(
154                iface, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets, 0L);
155    }
156
157    // @VisibleForTesting
158    public NetworkStats addValues(String iface, int uid, int set, int tag, long rxBytes,
159            long rxPackets, long txBytes, long txPackets, long operations) {
160        return addValues(new Entry(
161                iface, uid, set, tag, rxBytes, rxPackets, txBytes, txPackets, operations));
162    }
163
164    /**
165     * Add new stats entry, copying from given {@link Entry}. The {@link Entry}
166     * object can be recycled across multiple calls.
167     */
168    public NetworkStats addValues(Entry entry) {
169        if (size >= this.iface.length) {
170            final int newLength = Math.max(iface.length, 10) * 3 / 2;
171            iface = Arrays.copyOf(iface, newLength);
172            uid = Arrays.copyOf(uid, newLength);
173            set = Arrays.copyOf(set, newLength);
174            tag = Arrays.copyOf(tag, newLength);
175            rxBytes = Arrays.copyOf(rxBytes, newLength);
176            rxPackets = Arrays.copyOf(rxPackets, newLength);
177            txBytes = Arrays.copyOf(txBytes, newLength);
178            txPackets = Arrays.copyOf(txPackets, newLength);
179            operations = Arrays.copyOf(operations, newLength);
180        }
181
182        iface[size] = entry.iface;
183        uid[size] = entry.uid;
184        set[size] = entry.set;
185        tag[size] = entry.tag;
186        rxBytes[size] = entry.rxBytes;
187        rxPackets[size] = entry.rxPackets;
188        txBytes[size] = entry.txBytes;
189        txPackets[size] = entry.txPackets;
190        operations[size] = entry.operations;
191        size++;
192
193        return this;
194    }
195
196    /**
197     * Return specific stats entry.
198     */
199    public Entry getValues(int i, Entry recycle) {
200        final Entry entry = recycle != null ? recycle : new Entry();
201        entry.iface = iface[i];
202        entry.uid = uid[i];
203        entry.set = set[i];
204        entry.tag = tag[i];
205        entry.rxBytes = rxBytes[i];
206        entry.rxPackets = rxPackets[i];
207        entry.txBytes = txBytes[i];
208        entry.txPackets = txPackets[i];
209        entry.operations = operations[i];
210        return entry;
211    }
212
213    public long getElapsedRealtime() {
214        return elapsedRealtime;
215    }
216
217    public int size() {
218        return size;
219    }
220
221    // @VisibleForTesting
222    public int internalSize() {
223        return iface.length;
224    }
225
226    @Deprecated
227    public NetworkStats combineValues(String iface, int uid, int tag, long rxBytes, long rxPackets,
228            long txBytes, long txPackets, long operations) {
229        return combineValues(
230                iface, uid, SET_DEFAULT, tag, rxBytes, rxPackets, txBytes, txPackets, operations);
231    }
232
233    public NetworkStats combineValues(String iface, int uid, int set, int tag, long rxBytes,
234            long rxPackets, long txBytes, long txPackets, long operations) {
235        return combineValues(new Entry(
236                iface, uid, set, tag, rxBytes, rxPackets, txBytes, txPackets, operations));
237    }
238
239    /**
240     * Combine given values with an existing row, or create a new row if
241     * {@link #findIndex(String, int, int, int)} is unable to find match. Can
242     * also be used to subtract values from existing rows.
243     */
244    public NetworkStats combineValues(Entry entry) {
245        final int i = findIndex(entry.iface, entry.uid, entry.set, entry.tag);
246        if (i == -1) {
247            // only create new entry when positive contribution
248            addValues(entry);
249        } else {
250            rxBytes[i] += entry.rxBytes;
251            rxPackets[i] += entry.rxPackets;
252            txBytes[i] += entry.txBytes;
253            txPackets[i] += entry.txPackets;
254            operations[i] += entry.operations;
255        }
256        return this;
257    }
258
259    /**
260     * Find first stats index that matches the requested parameters.
261     */
262    public int findIndex(String iface, int uid, int set, int tag) {
263        for (int i = 0; i < size; i++) {
264            if (Objects.equal(iface, this.iface[i]) && uid == this.uid[i] && set == this.set[i]
265                    && tag == this.tag[i]) {
266                return i;
267            }
268        }
269        return -1;
270    }
271
272    /**
273     * Splice in {@link #operations} from the given {@link NetworkStats} based
274     * on matching {@link #uid} and {@link #tag} rows. Ignores {@link #iface},
275     * since operation counts are at data layer.
276     */
277    public void spliceOperationsFrom(NetworkStats stats) {
278        for (int i = 0; i < size; i++) {
279            final int j = stats.findIndex(IFACE_ALL, uid[i], set[i], tag[i]);
280            if (j == -1) {
281                operations[i] = 0;
282            } else {
283                operations[i] = stats.operations[j];
284            }
285        }
286    }
287
288    /**
289     * Return list of unique interfaces known by this data structure.
290     */
291    public String[] getUniqueIfaces() {
292        final HashSet<String> ifaces = new HashSet<String>();
293        for (String iface : this.iface) {
294            if (iface != IFACE_ALL) {
295                ifaces.add(iface);
296            }
297        }
298        return ifaces.toArray(new String[ifaces.size()]);
299    }
300
301    /**
302     * Return list of unique UIDs known by this data structure.
303     */
304    public int[] getUniqueUids() {
305        final SparseBooleanArray uids = new SparseBooleanArray();
306        for (int uid : this.uid) {
307            uids.put(uid, true);
308        }
309
310        final int size = uids.size();
311        final int[] result = new int[size];
312        for (int i = 0; i < size; i++) {
313            result[i] = uids.keyAt(i);
314        }
315        return result;
316    }
317
318    /**
319     * Return total bytes represented by this snapshot object, usually used when
320     * checking if a {@link #subtract(NetworkStats)} delta passes a threshold.
321     */
322    public long getTotalBytes() {
323        final Entry entry = getTotal(null);
324        return entry.rxBytes + entry.txBytes;
325    }
326
327    /**
328     * Return total of all fields represented by this snapshot object.
329     */
330    public Entry getTotal(Entry recycle) {
331        final Entry entry = recycle != null ? recycle : new Entry();
332
333        entry.iface = IFACE_ALL;
334        entry.uid = UID_ALL;
335        entry.set = SET_ALL;
336        entry.tag = TAG_NONE;
337        entry.rxBytes = 0;
338        entry.rxPackets = 0;
339        entry.txBytes = 0;
340        entry.txPackets = 0;
341
342        for (int i = 0; i < size; i++) {
343            // skip specific tags, since already counted in TAG_NONE
344            if (tag[i] != TAG_NONE) continue;
345
346            entry.rxBytes += rxBytes[i];
347            entry.rxPackets += rxPackets[i];
348            entry.txBytes += txBytes[i];
349            entry.txPackets += txPackets[i];
350            entry.operations += operations[i];
351        }
352        return entry;
353    }
354
355    /**
356     * Subtract the given {@link NetworkStats}, effectively leaving the delta
357     * between two snapshots in time. Assumes that statistics rows collect over
358     * time, and that none of them have disappeared.
359     *
360     * @throws IllegalArgumentException when given {@link NetworkStats} is
361     *             non-monotonic.
362     */
363    public NetworkStats subtract(NetworkStats value) {
364        return subtract(value, true, false);
365    }
366
367    /**
368     * Subtract the given {@link NetworkStats}, effectively leaving the delta
369     * between two snapshots in time. Assumes that statistics rows collect over
370     * time, and that none of them have disappeared.
371     * <p>
372     * Instead of throwing when counters are non-monotonic, this variant clamps
373     * results to never be negative.
374     */
375    public NetworkStats subtractClamped(NetworkStats value) {
376        return subtract(value, false, true);
377    }
378
379    /**
380     * Subtract the given {@link NetworkStats}, effectively leaving the delta
381     * between two snapshots in time. Assumes that statistics rows collect over
382     * time, and that none of them have disappeared.
383     *
384     * @param enforceMonotonic Validate that incoming value is strictly
385     *            monotonic compared to this object.
386     * @param clampNegative Instead of throwing like {@code enforceMonotonic},
387     *            clamp resulting counters at 0 to prevent negative values.
388     */
389    private NetworkStats subtract(
390            NetworkStats value, boolean enforceMonotonic, boolean clampNegative) {
391        final long deltaRealtime = this.elapsedRealtime - value.elapsedRealtime;
392        if (enforceMonotonic && deltaRealtime < 0) {
393            throw new IllegalArgumentException("found non-monotonic realtime");
394        }
395
396        // result will have our rows, and elapsed time between snapshots
397        final Entry entry = new Entry();
398        final NetworkStats result = new NetworkStats(deltaRealtime, size);
399        for (int i = 0; i < size; i++) {
400            entry.iface = iface[i];
401            entry.uid = uid[i];
402            entry.set = set[i];
403            entry.tag = tag[i];
404
405            // find remote row that matches, and subtract
406            final int j = value.findIndex(entry.iface, entry.uid, entry.set, entry.tag);
407            if (j == -1) {
408                // newly appearing row, return entire value
409                entry.rxBytes = rxBytes[i];
410                entry.rxPackets = rxPackets[i];
411                entry.txBytes = txBytes[i];
412                entry.txPackets = txPackets[i];
413                entry.operations = operations[i];
414            } else {
415                // existing row, subtract remote value
416                entry.rxBytes = rxBytes[i] - value.rxBytes[j];
417                entry.rxPackets = rxPackets[i] - value.rxPackets[j];
418                entry.txBytes = txBytes[i] - value.txBytes[j];
419                entry.txPackets = txPackets[i] - value.txPackets[j];
420                entry.operations = operations[i] - value.operations[j];
421                if (enforceMonotonic
422                        && (entry.rxBytes < 0 || entry.rxPackets < 0 || entry.txBytes < 0
423                                || entry.txPackets < 0 || entry.operations < 0)) {
424                    Log.v(TAG, "lhs=" + this);
425                    Log.v(TAG, "rhs=" + value);
426                    throw new IllegalArgumentException(
427                            "found non-monotonic values at lhs[" + i + "] - rhs[" + j + "]");
428                }
429                if (clampNegative) {
430                    entry.rxBytes = Math.max(0, entry.rxBytes);
431                    entry.rxPackets = Math.max(0, entry.rxPackets);
432                    entry.txBytes = Math.max(0, entry.txBytes);
433                    entry.txPackets = Math.max(0, entry.txPackets);
434                    entry.operations = Math.max(0, entry.operations);
435                }
436            }
437
438            result.addValues(entry);
439        }
440
441        return result;
442    }
443
444    public void dump(String prefix, PrintWriter pw) {
445        pw.print(prefix);
446        pw.print("NetworkStats: elapsedRealtime="); pw.println(elapsedRealtime);
447        for (int i = 0; i < size; i++) {
448            pw.print(prefix);
449            pw.print("  iface="); pw.print(iface[i]);
450            pw.print(" uid="); pw.print(uid[i]);
451            pw.print(" set="); pw.print(setToString(set[i]));
452            pw.print(" tag="); pw.print(tagToString(tag[i]));
453            pw.print(" rxBytes="); pw.print(rxBytes[i]);
454            pw.print(" rxPackets="); pw.print(rxPackets[i]);
455            pw.print(" txBytes="); pw.print(txBytes[i]);
456            pw.print(" txPackets="); pw.print(txPackets[i]);
457            pw.print(" operations="); pw.println(operations[i]);
458        }
459    }
460
461    /**
462     * Return text description of {@link #set} value.
463     */
464    public static String setToString(int set) {
465        switch (set) {
466            case SET_ALL:
467                return "ALL";
468            case SET_DEFAULT:
469                return "DEFAULT";
470            case SET_FOREGROUND:
471                return "FOREGROUND";
472            default:
473                return "UNKNOWN";
474        }
475    }
476
477    /**
478     * Return text description of {@link #tag} value.
479     */
480    public static String tagToString(int tag) {
481        return "0x" + Integer.toHexString(tag);
482    }
483
484    @Override
485    public String toString() {
486        final CharArrayWriter writer = new CharArrayWriter();
487        dump("", new PrintWriter(writer));
488        return writer.toString();
489    }
490
491    /** {@inheritDoc} */
492    public int describeContents() {
493        return 0;
494    }
495
496    public static final Creator<NetworkStats> CREATOR = new Creator<NetworkStats>() {
497        public NetworkStats createFromParcel(Parcel in) {
498            return new NetworkStats(in);
499        }
500
501        public NetworkStats[] newArray(int size) {
502            return new NetworkStats[size];
503        }
504    };
505}
506