NetworkStats.java revision 47eb102b40cd1324d89816a7fb0fecd14fd7a408
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        long totalBytes = 0;
324        for (int i = 0; i < size; i++) {
325            // skip specific tags, since already counted in TAG_NONE
326            if (tag[i] != TAG_NONE) continue;
327
328            totalBytes += rxBytes[i];
329            totalBytes += txBytes[i];
330        }
331        return totalBytes;
332    }
333
334    /**
335     * Subtract the given {@link NetworkStats}, effectively leaving the delta
336     * between two snapshots in time. Assumes that statistics rows collect over
337     * time, and that none of them have disappeared.
338     *
339     * @throws IllegalArgumentException when given {@link NetworkStats} is
340     *             non-monotonic.
341     */
342    public NetworkStats subtract(NetworkStats value) {
343        return subtract(value, true, false);
344    }
345
346    /**
347     * Subtract the given {@link NetworkStats}, effectively leaving the delta
348     * between two snapshots in time. Assumes that statistics rows collect over
349     * time, and that none of them have disappeared.
350     * <p>
351     * Instead of throwing when counters are non-monotonic, this variant clamps
352     * results to never be negative.
353     */
354    public NetworkStats subtractClamped(NetworkStats value) {
355        return subtract(value, false, true);
356    }
357
358    /**
359     * Subtract the given {@link NetworkStats}, effectively leaving the delta
360     * between two snapshots in time. Assumes that statistics rows collect over
361     * time, and that none of them have disappeared.
362     *
363     * @param enforceMonotonic Validate that incoming value is strictly
364     *            monotonic compared to this object.
365     * @param clampNegative Instead of throwing like {@code enforceMonotonic},
366     *            clamp resulting counters at 0 to prevent negative values.
367     */
368    private NetworkStats subtract(
369            NetworkStats value, boolean enforceMonotonic, boolean clampNegative) {
370        final long deltaRealtime = this.elapsedRealtime - value.elapsedRealtime;
371        if (enforceMonotonic && deltaRealtime < 0) {
372            throw new IllegalArgumentException("found non-monotonic realtime");
373        }
374
375        // result will have our rows, and elapsed time between snapshots
376        final Entry entry = new Entry();
377        final NetworkStats result = new NetworkStats(deltaRealtime, size);
378        for (int i = 0; i < size; i++) {
379            entry.iface = iface[i];
380            entry.uid = uid[i];
381            entry.set = set[i];
382            entry.tag = tag[i];
383
384            // find remote row that matches, and subtract
385            final int j = value.findIndex(entry.iface, entry.uid, entry.set, entry.tag);
386            if (j == -1) {
387                // newly appearing row, return entire value
388                entry.rxBytes = rxBytes[i];
389                entry.rxPackets = rxPackets[i];
390                entry.txBytes = txBytes[i];
391                entry.txPackets = txPackets[i];
392                entry.operations = operations[i];
393            } else {
394                // existing row, subtract remote value
395                entry.rxBytes = rxBytes[i] - value.rxBytes[j];
396                entry.rxPackets = rxPackets[i] - value.rxPackets[j];
397                entry.txBytes = txBytes[i] - value.txBytes[j];
398                entry.txPackets = txPackets[i] - value.txPackets[j];
399                entry.operations = operations[i] - value.operations[j];
400                if (enforceMonotonic
401                        && (entry.rxBytes < 0 || entry.rxPackets < 0 || entry.txBytes < 0
402                                || entry.txPackets < 0 || entry.operations < 0)) {
403                    Log.v(TAG, "lhs=" + this);
404                    Log.v(TAG, "rhs=" + value);
405                    throw new IllegalArgumentException(
406                            "found non-monotonic values at lhs[" + i + "] - rhs[" + j + "]");
407                }
408                if (clampNegative) {
409                    entry.rxBytes = Math.max(0, entry.rxBytes);
410                    entry.rxPackets = Math.max(0, entry.rxPackets);
411                    entry.txBytes = Math.max(0, entry.txBytes);
412                    entry.txPackets = Math.max(0, entry.txPackets);
413                    entry.operations = Math.max(0, entry.operations);
414                }
415            }
416
417            result.addValues(entry);
418        }
419
420        return result;
421    }
422
423    public void dump(String prefix, PrintWriter pw) {
424        pw.print(prefix);
425        pw.print("NetworkStats: elapsedRealtime="); pw.println(elapsedRealtime);
426        for (int i = 0; i < size; i++) {
427            pw.print(prefix);
428            pw.print("  iface="); pw.print(iface[i]);
429            pw.print(" uid="); pw.print(uid[i]);
430            pw.print(" set="); pw.print(setToString(set[i]));
431            pw.print(" tag="); pw.print(tagToString(tag[i]));
432            pw.print(" rxBytes="); pw.print(rxBytes[i]);
433            pw.print(" rxPackets="); pw.print(rxPackets[i]);
434            pw.print(" txBytes="); pw.print(txBytes[i]);
435            pw.print(" txPackets="); pw.print(txPackets[i]);
436            pw.print(" operations="); pw.println(operations[i]);
437        }
438    }
439
440    /**
441     * Return text description of {@link #set} value.
442     */
443    public static String setToString(int set) {
444        switch (set) {
445            case SET_ALL:
446                return "ALL";
447            case SET_DEFAULT:
448                return "DEFAULT";
449            case SET_FOREGROUND:
450                return "FOREGROUND";
451            default:
452                return "UNKNOWN";
453        }
454    }
455
456    /**
457     * Return text description of {@link #tag} value.
458     */
459    public static String tagToString(int tag) {
460        return "0x" + Integer.toHexString(tag);
461    }
462
463    @Override
464    public String toString() {
465        final CharArrayWriter writer = new CharArrayWriter();
466        dump("", new PrintWriter(writer));
467        return writer.toString();
468    }
469
470    /** {@inheritDoc} */
471    public int describeContents() {
472        return 0;
473    }
474
475    public static final Creator<NetworkStats> CREATOR = new Creator<NetworkStats>() {
476        public NetworkStats createFromParcel(Parcel in) {
477            return new NetworkStats(in);
478        }
479
480        public NetworkStats[] newArray(int size) {
481            return new NetworkStats[size];
482        }
483    };
484}
485