NetworkStats.java revision b3d5957604f8fcaafe72bd76052bc76b682bf443
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     * Find first stats index that matches the requested parameters.
276     */
277    public int findIndex(String iface, int uid, int set, int tag) {
278        for (int i = 0; i < size; i++) {
279            if (Objects.equal(iface, this.iface[i]) && uid == this.uid[i] && set == this.set[i]
280                    && tag == this.tag[i]) {
281                return i;
282            }
283        }
284        return -1;
285    }
286
287    /**
288     * Splice in {@link #operations} from the given {@link NetworkStats} based
289     * on matching {@link #uid} and {@link #tag} rows. Ignores {@link #iface},
290     * since operation counts are at data layer.
291     */
292    public void spliceOperationsFrom(NetworkStats stats) {
293        for (int i = 0; i < size; i++) {
294            final int j = stats.findIndex(IFACE_ALL, uid[i], set[i], tag[i]);
295            if (j == -1) {
296                operations[i] = 0;
297            } else {
298                operations[i] = stats.operations[j];
299            }
300        }
301    }
302
303    /**
304     * Return list of unique interfaces known by this data structure.
305     */
306    public String[] getUniqueIfaces() {
307        final HashSet<String> ifaces = new HashSet<String>();
308        for (String iface : this.iface) {
309            if (iface != IFACE_ALL) {
310                ifaces.add(iface);
311            }
312        }
313        return ifaces.toArray(new String[ifaces.size()]);
314    }
315
316    /**
317     * Return list of unique UIDs known by this data structure.
318     */
319    public int[] getUniqueUids() {
320        final SparseBooleanArray uids = new SparseBooleanArray();
321        for (int uid : this.uid) {
322            uids.put(uid, true);
323        }
324
325        final int size = uids.size();
326        final int[] result = new int[size];
327        for (int i = 0; i < size; i++) {
328            result[i] = uids.keyAt(i);
329        }
330        return result;
331    }
332
333    /**
334     * Return total bytes represented by this snapshot object, usually used when
335     * checking if a {@link #subtract(NetworkStats)} delta passes a threshold.
336     */
337    public long getTotalBytes() {
338        final Entry entry = getTotal(null);
339        return entry.rxBytes + entry.txBytes;
340    }
341
342    /**
343     * Return total of all fields represented by this snapshot object.
344     */
345    public Entry getTotal(Entry recycle) {
346        final Entry entry = recycle != null ? recycle : new Entry();
347
348        entry.iface = IFACE_ALL;
349        entry.uid = UID_ALL;
350        entry.set = SET_ALL;
351        entry.tag = TAG_NONE;
352        entry.rxBytes = 0;
353        entry.rxPackets = 0;
354        entry.txBytes = 0;
355        entry.txPackets = 0;
356
357        for (int i = 0; i < size; i++) {
358            // skip specific tags, since already counted in TAG_NONE
359            if (tag[i] != TAG_NONE) continue;
360
361            entry.rxBytes += rxBytes[i];
362            entry.rxPackets += rxPackets[i];
363            entry.txBytes += txBytes[i];
364            entry.txPackets += txPackets[i];
365            entry.operations += operations[i];
366        }
367        return entry;
368    }
369
370    /**
371     * Subtract the given {@link NetworkStats}, effectively leaving the delta
372     * between two snapshots in time. Assumes that statistics rows collect over
373     * time, and that none of them have disappeared.
374     *
375     * @throws IllegalArgumentException when given {@link NetworkStats} is
376     *             non-monotonic.
377     */
378    public NetworkStats subtract(NetworkStats value) {
379        return subtract(value, true, false);
380    }
381
382    /**
383     * Subtract the given {@link NetworkStats}, effectively leaving the delta
384     * between two snapshots in time. Assumes that statistics rows collect over
385     * time, and that none of them have disappeared.
386     * <p>
387     * Instead of throwing when counters are non-monotonic, this variant clamps
388     * results to never be negative.
389     */
390    public NetworkStats subtractClamped(NetworkStats value) {
391        return subtract(value, false, true);
392    }
393
394    /**
395     * Subtract the given {@link NetworkStats}, effectively leaving the delta
396     * between two snapshots in time. Assumes that statistics rows collect over
397     * time, and that none of them have disappeared.
398     *
399     * @param enforceMonotonic Validate that incoming value is strictly
400     *            monotonic compared to this object.
401     * @param clampNegative Instead of throwing like {@code enforceMonotonic},
402     *            clamp resulting counters at 0 to prevent negative values.
403     */
404    private NetworkStats subtract(
405            NetworkStats value, boolean enforceMonotonic, boolean clampNegative) {
406        final long deltaRealtime = this.elapsedRealtime - value.elapsedRealtime;
407        if (enforceMonotonic && deltaRealtime < 0) {
408            throw new IllegalArgumentException("found non-monotonic realtime");
409        }
410
411        // result will have our rows, and elapsed time between snapshots
412        final Entry entry = new Entry();
413        final NetworkStats result = new NetworkStats(deltaRealtime, size);
414        for (int i = 0; i < size; i++) {
415            entry.iface = iface[i];
416            entry.uid = uid[i];
417            entry.set = set[i];
418            entry.tag = tag[i];
419
420            // find remote row that matches, and subtract
421            final int j = value.findIndex(entry.iface, entry.uid, entry.set, entry.tag);
422            if (j == -1) {
423                // newly appearing row, return entire value
424                entry.rxBytes = rxBytes[i];
425                entry.rxPackets = rxPackets[i];
426                entry.txBytes = txBytes[i];
427                entry.txPackets = txPackets[i];
428                entry.operations = operations[i];
429            } else {
430                // existing row, subtract remote value
431                entry.rxBytes = rxBytes[i] - value.rxBytes[j];
432                entry.rxPackets = rxPackets[i] - value.rxPackets[j];
433                entry.txBytes = txBytes[i] - value.txBytes[j];
434                entry.txPackets = txPackets[i] - value.txPackets[j];
435                entry.operations = operations[i] - value.operations[j];
436                if (enforceMonotonic
437                        && (entry.rxBytes < 0 || entry.rxPackets < 0 || entry.txBytes < 0
438                                || entry.txPackets < 0 || entry.operations < 0)) {
439                    Log.v(TAG, "lhs=" + this);
440                    Log.v(TAG, "rhs=" + value);
441                    throw new IllegalArgumentException(
442                            "found non-monotonic values at lhs[" + i + "] - rhs[" + j + "]");
443                }
444                if (clampNegative) {
445                    entry.rxBytes = Math.max(0, entry.rxBytes);
446                    entry.rxPackets = Math.max(0, entry.rxPackets);
447                    entry.txBytes = Math.max(0, entry.txBytes);
448                    entry.txPackets = Math.max(0, entry.txPackets);
449                    entry.operations = Math.max(0, entry.operations);
450                }
451            }
452
453            result.addValues(entry);
454        }
455
456        return result;
457    }
458
459    public void dump(String prefix, PrintWriter pw) {
460        pw.print(prefix);
461        pw.print("NetworkStats: elapsedRealtime="); pw.println(elapsedRealtime);
462        for (int i = 0; i < size; i++) {
463            pw.print(prefix);
464            pw.print("  iface="); pw.print(iface[i]);
465            pw.print(" uid="); pw.print(uid[i]);
466            pw.print(" set="); pw.print(setToString(set[i]));
467            pw.print(" tag="); pw.print(tagToString(tag[i]));
468            pw.print(" rxBytes="); pw.print(rxBytes[i]);
469            pw.print(" rxPackets="); pw.print(rxPackets[i]);
470            pw.print(" txBytes="); pw.print(txBytes[i]);
471            pw.print(" txPackets="); pw.print(txPackets[i]);
472            pw.print(" operations="); pw.println(operations[i]);
473        }
474    }
475
476    /**
477     * Return text description of {@link #set} value.
478     */
479    public static String setToString(int set) {
480        switch (set) {
481            case SET_ALL:
482                return "ALL";
483            case SET_DEFAULT:
484                return "DEFAULT";
485            case SET_FOREGROUND:
486                return "FOREGROUND";
487            default:
488                return "UNKNOWN";
489        }
490    }
491
492    /**
493     * Return text description of {@link #tag} value.
494     */
495    public static String tagToString(int tag) {
496        return "0x" + Integer.toHexString(tag);
497    }
498
499    @Override
500    public String toString() {
501        final CharArrayWriter writer = new CharArrayWriter();
502        dump("", new PrintWriter(writer));
503        return writer.toString();
504    }
505
506    /** {@inheritDoc} */
507    public int describeContents() {
508        return 0;
509    }
510
511    public static final Creator<NetworkStats> CREATOR = new Creator<NetworkStats>() {
512        public NetworkStats createFromParcel(Parcel in) {
513            return new NetworkStats(in);
514        }
515
516        public NetworkStats[] newArray(int size) {
517            return new NetworkStats[size];
518        }
519    };
520}
521