NetworkStats.java revision 905b5891d2aa802f447ac2ce5d77b6c5ba06277a
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        @Override
107        public String toString() {
108            final StringBuilder builder = new StringBuilder();
109            builder.append("iface=").append(iface);
110            builder.append(" uid=").append(uid);
111            builder.append(" set=").append(setToString(set));
112            builder.append(" tag=").append(tagToString(tag));
113            builder.append(" rxBytes=").append(rxBytes);
114            builder.append(" rxPackets=").append(rxPackets);
115            builder.append(" txBytes=").append(txBytes);
116            builder.append(" txPackets=").append(txPackets);
117            builder.append(" operations=").append(operations);
118            return builder.toString();
119        }
120    }
121
122    public NetworkStats(long elapsedRealtime, int initialSize) {
123        this.elapsedRealtime = elapsedRealtime;
124        this.size = 0;
125        this.iface = new String[initialSize];
126        this.uid = new int[initialSize];
127        this.set = new int[initialSize];
128        this.tag = new int[initialSize];
129        this.rxBytes = new long[initialSize];
130        this.rxPackets = new long[initialSize];
131        this.txBytes = new long[initialSize];
132        this.txPackets = new long[initialSize];
133        this.operations = new long[initialSize];
134    }
135
136    public NetworkStats(Parcel parcel) {
137        elapsedRealtime = parcel.readLong();
138        size = parcel.readInt();
139        iface = parcel.createStringArray();
140        uid = parcel.createIntArray();
141        set = parcel.createIntArray();
142        tag = parcel.createIntArray();
143        rxBytes = parcel.createLongArray();
144        rxPackets = parcel.createLongArray();
145        txBytes = parcel.createLongArray();
146        txPackets = parcel.createLongArray();
147        operations = parcel.createLongArray();
148    }
149
150    /** {@inheritDoc} */
151    public void writeToParcel(Parcel dest, int flags) {
152        dest.writeLong(elapsedRealtime);
153        dest.writeInt(size);
154        dest.writeStringArray(iface);
155        dest.writeIntArray(uid);
156        dest.writeIntArray(set);
157        dest.writeIntArray(tag);
158        dest.writeLongArray(rxBytes);
159        dest.writeLongArray(rxPackets);
160        dest.writeLongArray(txBytes);
161        dest.writeLongArray(txPackets);
162        dest.writeLongArray(operations);
163    }
164
165    // @VisibleForTesting
166    public NetworkStats addIfaceValues(
167            String iface, long rxBytes, long rxPackets, long txBytes, long txPackets) {
168        return addValues(
169                iface, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets, 0L);
170    }
171
172    // @VisibleForTesting
173    public NetworkStats addValues(String iface, int uid, int set, int tag, long rxBytes,
174            long rxPackets, long txBytes, long txPackets, long operations) {
175        return addValues(new Entry(
176                iface, uid, set, tag, rxBytes, rxPackets, txBytes, txPackets, operations));
177    }
178
179    /**
180     * Add new stats entry, copying from given {@link Entry}. The {@link Entry}
181     * object can be recycled across multiple calls.
182     */
183    public NetworkStats addValues(Entry entry) {
184        if (size >= this.iface.length) {
185            final int newLength = Math.max(iface.length, 10) * 3 / 2;
186            iface = Arrays.copyOf(iface, newLength);
187            uid = Arrays.copyOf(uid, newLength);
188            set = Arrays.copyOf(set, newLength);
189            tag = Arrays.copyOf(tag, newLength);
190            rxBytes = Arrays.copyOf(rxBytes, newLength);
191            rxPackets = Arrays.copyOf(rxPackets, newLength);
192            txBytes = Arrays.copyOf(txBytes, newLength);
193            txPackets = Arrays.copyOf(txPackets, newLength);
194            operations = Arrays.copyOf(operations, newLength);
195        }
196
197        iface[size] = entry.iface;
198        uid[size] = entry.uid;
199        set[size] = entry.set;
200        tag[size] = entry.tag;
201        rxBytes[size] = entry.rxBytes;
202        rxPackets[size] = entry.rxPackets;
203        txBytes[size] = entry.txBytes;
204        txPackets[size] = entry.txPackets;
205        operations[size] = entry.operations;
206        size++;
207
208        return this;
209    }
210
211    /**
212     * Return specific stats entry.
213     */
214    public Entry getValues(int i, Entry recycle) {
215        final Entry entry = recycle != null ? recycle : new Entry();
216        entry.iface = iface[i];
217        entry.uid = uid[i];
218        entry.set = set[i];
219        entry.tag = tag[i];
220        entry.rxBytes = rxBytes[i];
221        entry.rxPackets = rxPackets[i];
222        entry.txBytes = txBytes[i];
223        entry.txPackets = txPackets[i];
224        entry.operations = operations[i];
225        return entry;
226    }
227
228    public long getElapsedRealtime() {
229        return elapsedRealtime;
230    }
231
232    public int size() {
233        return size;
234    }
235
236    // @VisibleForTesting
237    public int internalSize() {
238        return iface.length;
239    }
240
241    @Deprecated
242    public NetworkStats combineValues(String iface, int uid, int tag, long rxBytes, long rxPackets,
243            long txBytes, long txPackets, long operations) {
244        return combineValues(
245                iface, uid, SET_DEFAULT, tag, rxBytes, rxPackets, txBytes, txPackets, operations);
246    }
247
248    public NetworkStats combineValues(String iface, int uid, int set, int tag, long rxBytes,
249            long rxPackets, long txBytes, long txPackets, long operations) {
250        return combineValues(new Entry(
251                iface, uid, set, tag, rxBytes, rxPackets, txBytes, txPackets, operations));
252    }
253
254    /**
255     * Combine given values with an existing row, or create a new row if
256     * {@link #findIndex(String, int, int, int)} is unable to find match. Can
257     * also be used to subtract values from existing rows.
258     */
259    public NetworkStats combineValues(Entry entry) {
260        final int i = findIndex(entry.iface, entry.uid, entry.set, entry.tag);
261        if (i == -1) {
262            // only create new entry when positive contribution
263            addValues(entry);
264        } else {
265            rxBytes[i] += entry.rxBytes;
266            rxPackets[i] += entry.rxPackets;
267            txBytes[i] += entry.txBytes;
268            txPackets[i] += entry.txPackets;
269            operations[i] += entry.operations;
270        }
271        return this;
272    }
273
274    /**
275     * Combine all values from another {@link NetworkStats} into this object.
276     */
277    public void combineAllValues(NetworkStats another) {
278        NetworkStats.Entry entry = null;
279        for (int i = 0; i < another.size; i++) {
280            entry = another.getValues(i, entry);
281            combineValues(entry);
282        }
283    }
284
285    /**
286     * Find first stats index that matches the requested parameters.
287     */
288    public int findIndex(String iface, int uid, int set, int tag) {
289        for (int i = 0; i < size; i++) {
290            if (Objects.equal(iface, this.iface[i]) && uid == this.uid[i] && set == this.set[i]
291                    && tag == this.tag[i]) {
292                return i;
293            }
294        }
295        return -1;
296    }
297
298    /**
299     * Splice in {@link #operations} from the given {@link NetworkStats} based
300     * on matching {@link #uid} and {@link #tag} rows. Ignores {@link #iface},
301     * since operation counts are at data layer.
302     */
303    public void spliceOperationsFrom(NetworkStats stats) {
304        for (int i = 0; i < size; i++) {
305            final int j = stats.findIndex(IFACE_ALL, uid[i], set[i], tag[i]);
306            if (j == -1) {
307                operations[i] = 0;
308            } else {
309                operations[i] = stats.operations[j];
310            }
311        }
312    }
313
314    /**
315     * Return list of unique interfaces known by this data structure.
316     */
317    public String[] getUniqueIfaces() {
318        final HashSet<String> ifaces = new HashSet<String>();
319        for (String iface : this.iface) {
320            if (iface != IFACE_ALL) {
321                ifaces.add(iface);
322            }
323        }
324        return ifaces.toArray(new String[ifaces.size()]);
325    }
326
327    /**
328     * Return list of unique UIDs known by this data structure.
329     */
330    public int[] getUniqueUids() {
331        final SparseBooleanArray uids = new SparseBooleanArray();
332        for (int uid : this.uid) {
333            uids.put(uid, true);
334        }
335
336        final int size = uids.size();
337        final int[] result = new int[size];
338        for (int i = 0; i < size; i++) {
339            result[i] = uids.keyAt(i);
340        }
341        return result;
342    }
343
344    /**
345     * Return total bytes represented by this snapshot object, usually used when
346     * checking if a {@link #subtract(NetworkStats)} delta passes a threshold.
347     */
348    public long getTotalBytes() {
349        final Entry entry = getTotal(null);
350        return entry.rxBytes + entry.txBytes;
351    }
352
353    /**
354     * Return total of all fields represented by this snapshot object.
355     */
356    public Entry getTotal(Entry recycle) {
357        final Entry entry = recycle != null ? recycle : new Entry();
358
359        entry.iface = IFACE_ALL;
360        entry.uid = UID_ALL;
361        entry.set = SET_ALL;
362        entry.tag = TAG_NONE;
363        entry.rxBytes = 0;
364        entry.rxPackets = 0;
365        entry.txBytes = 0;
366        entry.txPackets = 0;
367
368        for (int i = 0; i < size; i++) {
369            // skip specific tags, since already counted in TAG_NONE
370            if (tag[i] != TAG_NONE) continue;
371
372            entry.rxBytes += rxBytes[i];
373            entry.rxPackets += rxPackets[i];
374            entry.txBytes += txBytes[i];
375            entry.txPackets += txPackets[i];
376            entry.operations += operations[i];
377        }
378        return entry;
379    }
380
381    /**
382     * Subtract the given {@link NetworkStats}, effectively leaving the delta
383     * between two snapshots in time. Assumes that statistics rows collect over
384     * time, and that none of them have disappeared.
385     *
386     * @throws IllegalArgumentException when given {@link NetworkStats} is
387     *             non-monotonic.
388     */
389    public NetworkStats subtract(NetworkStats value) {
390        return subtract(value, true, false);
391    }
392
393    /**
394     * Subtract the given {@link NetworkStats}, effectively leaving the delta
395     * between two snapshots in time. Assumes that statistics rows collect over
396     * time, and that none of them have disappeared.
397     * <p>
398     * Instead of throwing when counters are non-monotonic, this variant clamps
399     * results to never be negative.
400     */
401    public NetworkStats subtractClamped(NetworkStats value) {
402        return subtract(value, false, true);
403    }
404
405    /**
406     * Subtract the given {@link NetworkStats}, effectively leaving the delta
407     * between two snapshots in time. Assumes that statistics rows collect over
408     * time, and that none of them have disappeared.
409     *
410     * @param enforceMonotonic Validate that incoming value is strictly
411     *            monotonic compared to this object.
412     * @param clampNegative Instead of throwing like {@code enforceMonotonic},
413     *            clamp resulting counters at 0 to prevent negative values.
414     */
415    private NetworkStats subtract(
416            NetworkStats value, boolean enforceMonotonic, boolean clampNegative) {
417        final long deltaRealtime = this.elapsedRealtime - value.elapsedRealtime;
418        if (enforceMonotonic && deltaRealtime < 0) {
419            throw new IllegalArgumentException("found non-monotonic realtime");
420        }
421
422        // result will have our rows, and elapsed time between snapshots
423        final Entry entry = new Entry();
424        final NetworkStats result = new NetworkStats(deltaRealtime, size);
425        for (int i = 0; i < size; i++) {
426            entry.iface = iface[i];
427            entry.uid = uid[i];
428            entry.set = set[i];
429            entry.tag = tag[i];
430
431            // find remote row that matches, and subtract
432            final int j = value.findIndex(entry.iface, entry.uid, entry.set, entry.tag);
433            if (j == -1) {
434                // newly appearing row, return entire value
435                entry.rxBytes = rxBytes[i];
436                entry.rxPackets = rxPackets[i];
437                entry.txBytes = txBytes[i];
438                entry.txPackets = txPackets[i];
439                entry.operations = operations[i];
440            } else {
441                // existing row, subtract remote value
442                entry.rxBytes = rxBytes[i] - value.rxBytes[j];
443                entry.rxPackets = rxPackets[i] - value.rxPackets[j];
444                entry.txBytes = txBytes[i] - value.txBytes[j];
445                entry.txPackets = txPackets[i] - value.txPackets[j];
446                entry.operations = operations[i] - value.operations[j];
447                if (enforceMonotonic
448                        && (entry.rxBytes < 0 || entry.rxPackets < 0 || entry.txBytes < 0
449                                || entry.txPackets < 0 || entry.operations < 0)) {
450                    Log.v(TAG, "lhs=" + this);
451                    Log.v(TAG, "rhs=" + value);
452                    throw new IllegalArgumentException(
453                            "found non-monotonic values at lhs[" + i + "] - rhs[" + j + "]");
454                }
455                if (clampNegative) {
456                    entry.rxBytes = Math.max(0, entry.rxBytes);
457                    entry.rxPackets = Math.max(0, entry.rxPackets);
458                    entry.txBytes = Math.max(0, entry.txBytes);
459                    entry.txPackets = Math.max(0, entry.txPackets);
460                    entry.operations = Math.max(0, entry.operations);
461                }
462            }
463
464            result.addValues(entry);
465        }
466
467        return result;
468    }
469
470    /**
471     * Return total statistics grouped by {@link #iface}; doesn't mutate the
472     * original structure.
473     */
474    public NetworkStats groupedByIface() {
475        final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
476
477        final Entry entry = new Entry();
478        entry.uid = UID_ALL;
479        entry.set = SET_ALL;
480        entry.tag = TAG_NONE;
481        entry.operations = 0L;
482
483        for (int i = 0; i < size; i++) {
484            // skip specific tags, since already counted in TAG_NONE
485            if (tag[i] != TAG_NONE) continue;
486
487            entry.iface = iface[i];
488            entry.rxBytes = rxBytes[i];
489            entry.rxPackets = rxPackets[i];
490            entry.txBytes = txBytes[i];
491            entry.txPackets = txPackets[i];
492            stats.combineValues(entry);
493        }
494
495        return stats;
496    }
497
498    public void dump(String prefix, PrintWriter pw) {
499        pw.print(prefix);
500        pw.print("NetworkStats: elapsedRealtime="); pw.println(elapsedRealtime);
501        for (int i = 0; i < size; i++) {
502            pw.print(prefix);
503            pw.print("  iface="); pw.print(iface[i]);
504            pw.print(" uid="); pw.print(uid[i]);
505            pw.print(" set="); pw.print(setToString(set[i]));
506            pw.print(" tag="); pw.print(tagToString(tag[i]));
507            pw.print(" rxBytes="); pw.print(rxBytes[i]);
508            pw.print(" rxPackets="); pw.print(rxPackets[i]);
509            pw.print(" txBytes="); pw.print(txBytes[i]);
510            pw.print(" txPackets="); pw.print(txPackets[i]);
511            pw.print(" operations="); pw.println(operations[i]);
512        }
513    }
514
515    /**
516     * Return text description of {@link #set} value.
517     */
518    public static String setToString(int set) {
519        switch (set) {
520            case SET_ALL:
521                return "ALL";
522            case SET_DEFAULT:
523                return "DEFAULT";
524            case SET_FOREGROUND:
525                return "FOREGROUND";
526            default:
527                return "UNKNOWN";
528        }
529    }
530
531    /**
532     * Return text description of {@link #tag} value.
533     */
534    public static String tagToString(int tag) {
535        return "0x" + Integer.toHexString(tag);
536    }
537
538    @Override
539    public String toString() {
540        final CharArrayWriter writer = new CharArrayWriter();
541        dump("", new PrintWriter(writer));
542        return writer.toString();
543    }
544
545    /** {@inheritDoc} */
546    public int describeContents() {
547        return 0;
548    }
549
550    public static final Creator<NetworkStats> CREATOR = new Creator<NetworkStats>() {
551        public NetworkStats createFromParcel(Parcel in) {
552            return new NetworkStats(in);
553        }
554
555        public NetworkStats[] newArray(int size) {
556            return new NetworkStats[size];
557        }
558    };
559}
560