NetworkStats.java revision 1059c3c30ad96a15695c1a92ae8896e078a6309f
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    /**
233     * Return age of this {@link NetworkStats} object with respect to
234     * {@link SystemClock#elapsedRealtime()}.
235     */
236    public long getElapsedRealtimeAge() {
237        return SystemClock.elapsedRealtime() - elapsedRealtime;
238    }
239
240    public int size() {
241        return size;
242    }
243
244    // @VisibleForTesting
245    public int internalSize() {
246        return iface.length;
247    }
248
249    @Deprecated
250    public NetworkStats combineValues(String iface, int uid, int tag, long rxBytes, long rxPackets,
251            long txBytes, long txPackets, long operations) {
252        return combineValues(
253                iface, uid, SET_DEFAULT, tag, rxBytes, rxPackets, txBytes, txPackets, operations);
254    }
255
256    public NetworkStats combineValues(String iface, int uid, int set, int tag, long rxBytes,
257            long rxPackets, long txBytes, long txPackets, long operations) {
258        return combineValues(new Entry(
259                iface, uid, set, tag, rxBytes, rxPackets, txBytes, txPackets, operations));
260    }
261
262    /**
263     * Combine given values with an existing row, or create a new row if
264     * {@link #findIndex(String, int, int, int)} is unable to find match. Can
265     * also be used to subtract values from existing rows.
266     */
267    public NetworkStats combineValues(Entry entry) {
268        final int i = findIndex(entry.iface, entry.uid, entry.set, entry.tag);
269        if (i == -1) {
270            // only create new entry when positive contribution
271            addValues(entry);
272        } else {
273            rxBytes[i] += entry.rxBytes;
274            rxPackets[i] += entry.rxPackets;
275            txBytes[i] += entry.txBytes;
276            txPackets[i] += entry.txPackets;
277            operations[i] += entry.operations;
278        }
279        return this;
280    }
281
282    /**
283     * Combine all values from another {@link NetworkStats} into this object.
284     */
285    public void combineAllValues(NetworkStats another) {
286        NetworkStats.Entry entry = null;
287        for (int i = 0; i < another.size; i++) {
288            entry = another.getValues(i, entry);
289            combineValues(entry);
290        }
291    }
292
293    /**
294     * Find first stats index that matches the requested parameters.
295     */
296    public int findIndex(String iface, int uid, int set, int tag) {
297        for (int i = 0; i < size; i++) {
298            if (Objects.equal(iface, this.iface[i]) && uid == this.uid[i] && set == this.set[i]
299                    && tag == this.tag[i]) {
300                return i;
301            }
302        }
303        return -1;
304    }
305
306    /**
307     * Splice in {@link #operations} from the given {@link NetworkStats} based
308     * on matching {@link #uid} and {@link #tag} rows. Ignores {@link #iface},
309     * since operation counts are at data layer.
310     */
311    public void spliceOperationsFrom(NetworkStats stats) {
312        for (int i = 0; i < size; i++) {
313            final int j = stats.findIndex(IFACE_ALL, uid[i], set[i], tag[i]);
314            if (j == -1) {
315                operations[i] = 0;
316            } else {
317                operations[i] = stats.operations[j];
318            }
319        }
320    }
321
322    /**
323     * Return list of unique interfaces known by this data structure.
324     */
325    public String[] getUniqueIfaces() {
326        final HashSet<String> ifaces = new HashSet<String>();
327        for (String iface : this.iface) {
328            if (iface != IFACE_ALL) {
329                ifaces.add(iface);
330            }
331        }
332        return ifaces.toArray(new String[ifaces.size()]);
333    }
334
335    /**
336     * Return list of unique UIDs known by this data structure.
337     */
338    public int[] getUniqueUids() {
339        final SparseBooleanArray uids = new SparseBooleanArray();
340        for (int uid : this.uid) {
341            uids.put(uid, true);
342        }
343
344        final int size = uids.size();
345        final int[] result = new int[size];
346        for (int i = 0; i < size; i++) {
347            result[i] = uids.keyAt(i);
348        }
349        return result;
350    }
351
352    /**
353     * Return total bytes represented by this snapshot object, usually used when
354     * checking if a {@link #subtract(NetworkStats)} delta passes a threshold.
355     */
356    public long getTotalBytes() {
357        final Entry entry = getTotal(null);
358        return entry.rxBytes + entry.txBytes;
359    }
360
361    /**
362     * Return total of all fields represented by this snapshot object.
363     */
364    public Entry getTotal(Entry recycle) {
365        return getTotal(recycle, null, UID_ALL);
366    }
367
368    /**
369     * Return total of all fields represented by this snapshot object matching
370     * the requested {@link #uid}.
371     */
372    public Entry getTotal(Entry recycle, int limitUid) {
373        return getTotal(recycle, null, limitUid);
374    }
375
376    /**
377     * Return total of all fields represented by this snapshot object matching
378     * the requested {@link #iface}.
379     */
380    public Entry getTotal(Entry recycle, HashSet<String> limitIface) {
381        return getTotal(recycle, limitIface, UID_ALL);
382    }
383
384    /**
385     * Return total of all fields represented by this snapshot object matching
386     * the requested {@link #iface} and {@link #uid}.
387     *
388     * @param limitIface Set of {@link #iface} to include in total; or {@code
389     *            null} to include all ifaces.
390     */
391    private Entry getTotal(Entry recycle, HashSet<String> limitIface, int limitUid) {
392        final Entry entry = recycle != null ? recycle : new Entry();
393
394        entry.iface = IFACE_ALL;
395        entry.uid = limitUid;
396        entry.set = SET_ALL;
397        entry.tag = TAG_NONE;
398        entry.rxBytes = 0;
399        entry.rxPackets = 0;
400        entry.txBytes = 0;
401        entry.txPackets = 0;
402        entry.operations = 0;
403
404        for (int i = 0; i < size; i++) {
405            final boolean matchesUid = (limitUid == UID_ALL) || (limitUid == uid[i]);
406            final boolean matchesIface = (limitIface == null) || (limitIface.contains(iface[i]));
407
408            if (matchesUid && matchesIface) {
409                // skip specific tags, since already counted in TAG_NONE
410                if (tag[i] != TAG_NONE) continue;
411
412                entry.rxBytes += rxBytes[i];
413                entry.rxPackets += rxPackets[i];
414                entry.txBytes += txBytes[i];
415                entry.txPackets += txPackets[i];
416                entry.operations += operations[i];
417            }
418        }
419        return entry;
420    }
421
422    /**
423     * Subtract the given {@link NetworkStats}, effectively leaving the delta
424     * between two snapshots in time. Assumes that statistics rows collect over
425     * time, and that none of them have disappeared.
426     *
427     * @throws IllegalArgumentException when given {@link NetworkStats} is
428     *             non-monotonic.
429     */
430    public NetworkStats subtract(NetworkStats value) {
431        return subtract(value, true, false);
432    }
433
434    /**
435     * Subtract the given {@link NetworkStats}, effectively leaving the delta
436     * between two snapshots in time. Assumes that statistics rows collect over
437     * time, and that none of them have disappeared.
438     * <p>
439     * Instead of throwing when counters are non-monotonic, this variant clamps
440     * results to never be negative.
441     */
442    public NetworkStats subtractClamped(NetworkStats value) {
443        return subtract(value, false, true);
444    }
445
446    /**
447     * Subtract the given {@link NetworkStats}, effectively leaving the delta
448     * between two snapshots in time. Assumes that statistics rows collect over
449     * time, and that none of them have disappeared.
450     *
451     * @param enforceMonotonic Validate that incoming value is strictly
452     *            monotonic compared to this object.
453     * @param clampNegative Instead of throwing like {@code enforceMonotonic},
454     *            clamp resulting counters at 0 to prevent negative values.
455     */
456    private NetworkStats subtract(
457            NetworkStats value, boolean enforceMonotonic, boolean clampNegative) {
458        final long deltaRealtime = this.elapsedRealtime - value.elapsedRealtime;
459        if (enforceMonotonic && deltaRealtime < 0) {
460            throw new IllegalArgumentException("found non-monotonic realtime");
461        }
462
463        // result will have our rows, and elapsed time between snapshots
464        final Entry entry = new Entry();
465        final NetworkStats result = new NetworkStats(deltaRealtime, size);
466        for (int i = 0; i < size; i++) {
467            entry.iface = iface[i];
468            entry.uid = uid[i];
469            entry.set = set[i];
470            entry.tag = tag[i];
471
472            // find remote row that matches, and subtract
473            final int j = value.findIndex(entry.iface, entry.uid, entry.set, entry.tag);
474            if (j == -1) {
475                // newly appearing row, return entire value
476                entry.rxBytes = rxBytes[i];
477                entry.rxPackets = rxPackets[i];
478                entry.txBytes = txBytes[i];
479                entry.txPackets = txPackets[i];
480                entry.operations = operations[i];
481            } else {
482                // existing row, subtract remote value
483                entry.rxBytes = rxBytes[i] - value.rxBytes[j];
484                entry.rxPackets = rxPackets[i] - value.rxPackets[j];
485                entry.txBytes = txBytes[i] - value.txBytes[j];
486                entry.txPackets = txPackets[i] - value.txPackets[j];
487                entry.operations = operations[i] - value.operations[j];
488                if (enforceMonotonic
489                        && (entry.rxBytes < 0 || entry.rxPackets < 0 || entry.txBytes < 0
490                                || entry.txPackets < 0 || entry.operations < 0)) {
491                    Log.v(TAG, "lhs=" + this);
492                    Log.v(TAG, "rhs=" + value);
493                    throw new IllegalArgumentException(
494                            "found non-monotonic values at lhs[" + i + "] - rhs[" + j + "]");
495                }
496                if (clampNegative) {
497                    entry.rxBytes = Math.max(0, entry.rxBytes);
498                    entry.rxPackets = Math.max(0, entry.rxPackets);
499                    entry.txBytes = Math.max(0, entry.txBytes);
500                    entry.txPackets = Math.max(0, entry.txPackets);
501                    entry.operations = Math.max(0, entry.operations);
502                }
503            }
504
505            result.addValues(entry);
506        }
507
508        return result;
509    }
510
511    /**
512     * Return total statistics grouped by {@link #iface}; doesn't mutate the
513     * original structure.
514     */
515    public NetworkStats groupedByIface() {
516        final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
517
518        final Entry entry = new Entry();
519        entry.uid = UID_ALL;
520        entry.set = SET_ALL;
521        entry.tag = TAG_NONE;
522        entry.operations = 0L;
523
524        for (int i = 0; i < size; i++) {
525            // skip specific tags, since already counted in TAG_NONE
526            if (tag[i] != TAG_NONE) continue;
527
528            entry.iface = iface[i];
529            entry.rxBytes = rxBytes[i];
530            entry.rxPackets = rxPackets[i];
531            entry.txBytes = txBytes[i];
532            entry.txPackets = txPackets[i];
533            stats.combineValues(entry);
534        }
535
536        return stats;
537    }
538
539    /**
540     * Return total statistics grouped by {@link #uid}; doesn't mutate the
541     * original structure.
542     */
543    public NetworkStats groupedByUid() {
544        final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
545
546        final Entry entry = new Entry();
547        entry.iface = IFACE_ALL;
548        entry.set = SET_ALL;
549        entry.tag = TAG_NONE;
550
551        for (int i = 0; i < size; i++) {
552            // skip specific tags, since already counted in TAG_NONE
553            if (tag[i] != TAG_NONE) continue;
554
555            entry.uid = uid[i];
556            entry.rxBytes = rxBytes[i];
557            entry.rxPackets = rxPackets[i];
558            entry.txBytes = txBytes[i];
559            entry.txPackets = txPackets[i];
560            entry.operations = operations[i];
561            stats.combineValues(entry);
562        }
563
564        return stats;
565    }
566
567    public void dump(String prefix, PrintWriter pw) {
568        pw.print(prefix);
569        pw.print("NetworkStats: elapsedRealtime="); pw.println(elapsedRealtime);
570        for (int i = 0; i < size; i++) {
571            pw.print(prefix);
572            pw.print("  iface="); pw.print(iface[i]);
573            pw.print(" uid="); pw.print(uid[i]);
574            pw.print(" set="); pw.print(setToString(set[i]));
575            pw.print(" tag="); pw.print(tagToString(tag[i]));
576            pw.print(" rxBytes="); pw.print(rxBytes[i]);
577            pw.print(" rxPackets="); pw.print(rxPackets[i]);
578            pw.print(" txBytes="); pw.print(txBytes[i]);
579            pw.print(" txPackets="); pw.print(txPackets[i]);
580            pw.print(" operations="); pw.println(operations[i]);
581        }
582    }
583
584    /**
585     * Return text description of {@link #set} value.
586     */
587    public static String setToString(int set) {
588        switch (set) {
589            case SET_ALL:
590                return "ALL";
591            case SET_DEFAULT:
592                return "DEFAULT";
593            case SET_FOREGROUND:
594                return "FOREGROUND";
595            default:
596                return "UNKNOWN";
597        }
598    }
599
600    /**
601     * Return text description of {@link #tag} value.
602     */
603    public static String tagToString(int tag) {
604        return "0x" + Integer.toHexString(tag);
605    }
606
607    @Override
608    public String toString() {
609        final CharArrayWriter writer = new CharArrayWriter();
610        dump("", new PrintWriter(writer));
611        return writer.toString();
612    }
613
614    /** {@inheritDoc} */
615    public int describeContents() {
616        return 0;
617    }
618
619    public static final Creator<NetworkStats> CREATOR = new Creator<NetworkStats>() {
620        public NetworkStats createFromParcel(Parcel in) {
621            return new NetworkStats(in);
622        }
623
624        public NetworkStats[] newArray(int size) {
625            return new NetworkStats[size];
626        }
627    };
628}
629