NetworkStats.java revision 163e6443f27884a9bfcb9a48ef606dc635852c23
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 static com.android.internal.util.Preconditions.checkNotNull;
20
21import android.os.Parcel;
22import android.os.Parcelable;
23import android.os.SystemClock;
24import android.util.SparseBooleanArray;
25
26import com.android.internal.util.Objects;
27
28import java.io.CharArrayWriter;
29import java.io.PrintWriter;
30import java.util.Arrays;
31import java.util.HashSet;
32
33/**
34 * Collection of active network statistics. Can contain summary details across
35 * all interfaces, or details with per-UID granularity. Internally stores data
36 * as a large table, closely matching {@code /proc/} data format. This structure
37 * optimizes for rapid in-memory comparison, but consider using
38 * {@link NetworkStatsHistory} when persisting.
39 *
40 * @hide
41 */
42public class NetworkStats implements Parcelable {
43    private static final String TAG = "NetworkStats";
44
45    /** {@link #iface} value when interface details unavailable. */
46    public static final String IFACE_ALL = null;
47    /** {@link #uid} value when UID details unavailable. */
48    public static final int UID_ALL = -1;
49    /** {@link #set} value when all sets combined. */
50    public static final int SET_ALL = -1;
51    /** {@link #set} value where background data is accounted. */
52    public static final int SET_DEFAULT = 0;
53    /** {@link #set} value where foreground data is accounted. */
54    public static final int SET_FOREGROUND = 1;
55    /** {@link #tag} value for total data across all tags. */
56    public static final int TAG_NONE = 0;
57
58    // TODO: move fields to "mVariable" notation
59
60    /**
61     * {@link SystemClock#elapsedRealtime()} timestamp when this data was
62     * generated.
63     */
64    private final long elapsedRealtime;
65    private int size;
66    private String[] iface;
67    private int[] uid;
68    private int[] set;
69    private int[] tag;
70    private long[] rxBytes;
71    private long[] rxPackets;
72    private long[] txBytes;
73    private long[] txPackets;
74    private long[] operations;
75
76    public static class Entry {
77        public String iface;
78        public int uid;
79        public int set;
80        public int tag;
81        public long rxBytes;
82        public long rxPackets;
83        public long txBytes;
84        public long txPackets;
85        public long operations;
86
87        public Entry() {
88            this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
89        }
90
91        public Entry(long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) {
92            this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets,
93                    operations);
94        }
95
96        public Entry(String iface, int uid, int set, int tag, long rxBytes, long rxPackets,
97                long txBytes, long txPackets, long operations) {
98            this.iface = iface;
99            this.uid = uid;
100            this.set = set;
101            this.tag = tag;
102            this.rxBytes = rxBytes;
103            this.rxPackets = rxPackets;
104            this.txBytes = txBytes;
105            this.txPackets = txPackets;
106            this.operations = operations;
107        }
108
109        @Override
110        public String toString() {
111            final StringBuilder builder = new StringBuilder();
112            builder.append("iface=").append(iface);
113            builder.append(" uid=").append(uid);
114            builder.append(" set=").append(setToString(set));
115            builder.append(" tag=").append(tagToString(tag));
116            builder.append(" rxBytes=").append(rxBytes);
117            builder.append(" rxPackets=").append(rxPackets);
118            builder.append(" txBytes=").append(txBytes);
119            builder.append(" txPackets=").append(txPackets);
120            builder.append(" operations=").append(operations);
121            return builder.toString();
122        }
123    }
124
125    public NetworkStats(long elapsedRealtime, int initialSize) {
126        this.elapsedRealtime = elapsedRealtime;
127        this.size = 0;
128        this.iface = new String[initialSize];
129        this.uid = new int[initialSize];
130        this.set = new int[initialSize];
131        this.tag = new int[initialSize];
132        this.rxBytes = new long[initialSize];
133        this.rxPackets = new long[initialSize];
134        this.txBytes = new long[initialSize];
135        this.txPackets = new long[initialSize];
136        this.operations = new long[initialSize];
137    }
138
139    public NetworkStats(Parcel parcel) {
140        elapsedRealtime = parcel.readLong();
141        size = parcel.readInt();
142        iface = parcel.createStringArray();
143        uid = parcel.createIntArray();
144        set = parcel.createIntArray();
145        tag = parcel.createIntArray();
146        rxBytes = parcel.createLongArray();
147        rxPackets = parcel.createLongArray();
148        txBytes = parcel.createLongArray();
149        txPackets = parcel.createLongArray();
150        operations = parcel.createLongArray();
151    }
152
153    /** {@inheritDoc} */
154    public void writeToParcel(Parcel dest, int flags) {
155        dest.writeLong(elapsedRealtime);
156        dest.writeInt(size);
157        dest.writeStringArray(iface);
158        dest.writeIntArray(uid);
159        dest.writeIntArray(set);
160        dest.writeIntArray(tag);
161        dest.writeLongArray(rxBytes);
162        dest.writeLongArray(rxPackets);
163        dest.writeLongArray(txBytes);
164        dest.writeLongArray(txPackets);
165        dest.writeLongArray(operations);
166    }
167
168    // @VisibleForTesting
169    public NetworkStats addIfaceValues(
170            String iface, long rxBytes, long rxPackets, long txBytes, long txPackets) {
171        return addValues(
172                iface, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets, 0L);
173    }
174
175    // @VisibleForTesting
176    public NetworkStats addValues(String iface, int uid, int set, int tag, long rxBytes,
177            long rxPackets, long txBytes, long txPackets, long operations) {
178        return addValues(new Entry(
179                iface, uid, set, tag, rxBytes, rxPackets, txBytes, txPackets, operations));
180    }
181
182    /**
183     * Add new stats entry, copying from given {@link Entry}. The {@link Entry}
184     * object can be recycled across multiple calls.
185     */
186    public NetworkStats addValues(Entry entry) {
187        if (size >= this.iface.length) {
188            final int newLength = Math.max(iface.length, 10) * 3 / 2;
189            iface = Arrays.copyOf(iface, newLength);
190            uid = Arrays.copyOf(uid, newLength);
191            set = Arrays.copyOf(set, newLength);
192            tag = Arrays.copyOf(tag, newLength);
193            rxBytes = Arrays.copyOf(rxBytes, newLength);
194            rxPackets = Arrays.copyOf(rxPackets, newLength);
195            txBytes = Arrays.copyOf(txBytes, newLength);
196            txPackets = Arrays.copyOf(txPackets, newLength);
197            operations = Arrays.copyOf(operations, newLength);
198        }
199
200        iface[size] = entry.iface;
201        uid[size] = entry.uid;
202        set[size] = entry.set;
203        tag[size] = entry.tag;
204        rxBytes[size] = entry.rxBytes;
205        rxPackets[size] = entry.rxPackets;
206        txBytes[size] = entry.txBytes;
207        txPackets[size] = entry.txPackets;
208        operations[size] = entry.operations;
209        size++;
210
211        return this;
212    }
213
214    /**
215     * Return specific stats entry.
216     */
217    public Entry getValues(int i, Entry recycle) {
218        final Entry entry = recycle != null ? recycle : new Entry();
219        entry.iface = iface[i];
220        entry.uid = uid[i];
221        entry.set = set[i];
222        entry.tag = tag[i];
223        entry.rxBytes = rxBytes[i];
224        entry.rxPackets = rxPackets[i];
225        entry.txBytes = txBytes[i];
226        entry.txPackets = txPackets[i];
227        entry.operations = operations[i];
228        return entry;
229    }
230
231    public long getElapsedRealtime() {
232        return elapsedRealtime;
233    }
234
235    /**
236     * Return age of this {@link NetworkStats} object with respect to
237     * {@link SystemClock#elapsedRealtime()}.
238     */
239    public long getElapsedRealtimeAge() {
240        return SystemClock.elapsedRealtime() - elapsedRealtime;
241    }
242
243    public int size() {
244        return size;
245    }
246
247    // @VisibleForTesting
248    public int internalSize() {
249        return iface.length;
250    }
251
252    @Deprecated
253    public NetworkStats combineValues(String iface, int uid, int tag, long rxBytes, long rxPackets,
254            long txBytes, long txPackets, long operations) {
255        return combineValues(
256                iface, uid, SET_DEFAULT, tag, rxBytes, rxPackets, txBytes, txPackets, operations);
257    }
258
259    public NetworkStats combineValues(String iface, int uid, int set, int tag, long rxBytes,
260            long rxPackets, long txBytes, long txPackets, long operations) {
261        return combineValues(new Entry(
262                iface, uid, set, tag, rxBytes, rxPackets, txBytes, txPackets, operations));
263    }
264
265    /**
266     * Combine given values with an existing row, or create a new row if
267     * {@link #findIndex(String, int, int, int)} is unable to find match. Can
268     * also be used to subtract values from existing rows.
269     */
270    public NetworkStats combineValues(Entry entry) {
271        final int i = findIndex(entry.iface, entry.uid, entry.set, entry.tag);
272        if (i == -1) {
273            // only create new entry when positive contribution
274            addValues(entry);
275        } else {
276            rxBytes[i] += entry.rxBytes;
277            rxPackets[i] += entry.rxPackets;
278            txBytes[i] += entry.txBytes;
279            txPackets[i] += entry.txPackets;
280            operations[i] += entry.operations;
281        }
282        return this;
283    }
284
285    /**
286     * Combine all values from another {@link NetworkStats} into this object.
287     */
288    public void combineAllValues(NetworkStats another) {
289        NetworkStats.Entry entry = null;
290        for (int i = 0; i < another.size; i++) {
291            entry = another.getValues(i, entry);
292            combineValues(entry);
293        }
294    }
295
296    /**
297     * Find first stats index that matches the requested parameters.
298     */
299    public int findIndex(String iface, int uid, int set, int tag) {
300        for (int i = 0; i < size; i++) {
301            if (uid == this.uid[i] && set == this.set[i] && tag == this.tag[i]
302                    && Objects.equal(iface, this.iface[i])) {
303                return i;
304            }
305        }
306        return -1;
307    }
308
309    /**
310     * Find first stats index that matches the requested parameters, starting
311     * search around the hinted index as an optimization.
312     */
313    // @VisibleForTesting
314    public int findIndexHinted(String iface, int uid, int set, int tag, int hintIndex) {
315        for (int offset = 0; offset < size; offset++) {
316            final int halfOffset = offset / 2;
317
318            // search outwards from hint index, alternating forward and backward
319            final int i;
320            if (offset % 2 == 0) {
321                i = (hintIndex + halfOffset) % size;
322            } else {
323                i = (size + hintIndex - halfOffset - 1) % size;
324            }
325
326            if (uid == this.uid[i] && set == this.set[i] && tag == this.tag[i]
327                    && Objects.equal(iface, this.iface[i])) {
328                return i;
329            }
330        }
331        return -1;
332    }
333
334    /**
335     * Splice in {@link #operations} from the given {@link NetworkStats} based
336     * on matching {@link #uid} and {@link #tag} rows. Ignores {@link #iface},
337     * since operation counts are at data layer.
338     */
339    public void spliceOperationsFrom(NetworkStats stats) {
340        for (int i = 0; i < size; i++) {
341            final int j = stats.findIndex(IFACE_ALL, uid[i], set[i], tag[i]);
342            if (j == -1) {
343                operations[i] = 0;
344            } else {
345                operations[i] = stats.operations[j];
346            }
347        }
348    }
349
350    /**
351     * Return list of unique interfaces known by this data structure.
352     */
353    public String[] getUniqueIfaces() {
354        final HashSet<String> ifaces = new HashSet<String>();
355        for (String iface : this.iface) {
356            if (iface != IFACE_ALL) {
357                ifaces.add(iface);
358            }
359        }
360        return ifaces.toArray(new String[ifaces.size()]);
361    }
362
363    /**
364     * Return list of unique UIDs known by this data structure.
365     */
366    public int[] getUniqueUids() {
367        final SparseBooleanArray uids = new SparseBooleanArray();
368        for (int uid : this.uid) {
369            uids.put(uid, true);
370        }
371
372        final int size = uids.size();
373        final int[] result = new int[size];
374        for (int i = 0; i < size; i++) {
375            result[i] = uids.keyAt(i);
376        }
377        return result;
378    }
379
380    /**
381     * Return total bytes represented by this snapshot object, usually used when
382     * checking if a {@link #subtract(NetworkStats)} delta passes a threshold.
383     */
384    public long getTotalBytes() {
385        final Entry entry = getTotal(null);
386        return entry.rxBytes + entry.txBytes;
387    }
388
389    /**
390     * Return total of all fields represented by this snapshot object.
391     */
392    public Entry getTotal(Entry recycle) {
393        return getTotal(recycle, null, UID_ALL);
394    }
395
396    /**
397     * Return total of all fields represented by this snapshot object matching
398     * the requested {@link #uid}.
399     */
400    public Entry getTotal(Entry recycle, int limitUid) {
401        return getTotal(recycle, null, limitUid);
402    }
403
404    /**
405     * Return total of all fields represented by this snapshot object matching
406     * the requested {@link #iface}.
407     */
408    public Entry getTotal(Entry recycle, HashSet<String> limitIface) {
409        return getTotal(recycle, limitIface, UID_ALL);
410    }
411
412    /**
413     * Return total of all fields represented by this snapshot object matching
414     * the requested {@link #iface} and {@link #uid}.
415     *
416     * @param limitIface Set of {@link #iface} to include in total; or {@code
417     *            null} to include all ifaces.
418     */
419    private Entry getTotal(Entry recycle, HashSet<String> limitIface, int limitUid) {
420        final Entry entry = recycle != null ? recycle : new Entry();
421
422        entry.iface = IFACE_ALL;
423        entry.uid = limitUid;
424        entry.set = SET_ALL;
425        entry.tag = TAG_NONE;
426        entry.rxBytes = 0;
427        entry.rxPackets = 0;
428        entry.txBytes = 0;
429        entry.txPackets = 0;
430        entry.operations = 0;
431
432        for (int i = 0; i < size; i++) {
433            final boolean matchesUid = (limitUid == UID_ALL) || (limitUid == uid[i]);
434            final boolean matchesIface = (limitIface == null) || (limitIface.contains(iface[i]));
435
436            if (matchesUid && matchesIface) {
437                // skip specific tags, since already counted in TAG_NONE
438                if (tag[i] != TAG_NONE) continue;
439
440                entry.rxBytes += rxBytes[i];
441                entry.rxPackets += rxPackets[i];
442                entry.txBytes += txBytes[i];
443                entry.txPackets += txPackets[i];
444                entry.operations += operations[i];
445            }
446        }
447        return entry;
448    }
449
450    /**
451     * Subtract the given {@link NetworkStats}, effectively leaving the delta
452     * between two snapshots in time. Assumes that statistics rows collect over
453     * time, and that none of them have disappeared.
454     */
455    public NetworkStats subtract(NetworkStats value) throws NonMonotonicException {
456        final long deltaRealtime = this.elapsedRealtime - value.elapsedRealtime;
457        if (deltaRealtime < 0) {
458            throw new IllegalArgumentException("found non-monotonic realtime");
459        }
460
461        // result will have our rows, and elapsed time between snapshots
462        final Entry entry = new Entry();
463        final NetworkStats result = new NetworkStats(deltaRealtime, size);
464        for (int i = 0; i < size; i++) {
465            entry.iface = iface[i];
466            entry.uid = uid[i];
467            entry.set = set[i];
468            entry.tag = tag[i];
469
470            // find remote row that matches, and subtract
471            final int j = value.findIndexHinted(entry.iface, entry.uid, entry.set, entry.tag, i);
472            if (j == -1) {
473                // newly appearing row, return entire value
474                entry.rxBytes = rxBytes[i];
475                entry.rxPackets = rxPackets[i];
476                entry.txBytes = txBytes[i];
477                entry.txPackets = txPackets[i];
478                entry.operations = operations[i];
479            } else {
480                // existing row, subtract remote value
481                entry.rxBytes = rxBytes[i] - value.rxBytes[j];
482                entry.rxPackets = rxPackets[i] - value.rxPackets[j];
483                entry.txBytes = txBytes[i] - value.txBytes[j];
484                entry.txPackets = txPackets[i] - value.txPackets[j];
485                entry.operations = operations[i] - value.operations[j];
486
487                if (entry.rxBytes < 0 || entry.rxPackets < 0 || entry.txBytes < 0
488                        || entry.txPackets < 0 || entry.operations < 0) {
489                    throw new NonMonotonicException(this, i, value, j);
490                }
491            }
492
493            result.addValues(entry);
494        }
495
496        return result;
497    }
498
499    /**
500     * Return total statistics grouped by {@link #iface}; doesn't mutate the
501     * original structure.
502     */
503    public NetworkStats groupedByIface() {
504        final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
505
506        final Entry entry = new Entry();
507        entry.uid = UID_ALL;
508        entry.set = SET_ALL;
509        entry.tag = TAG_NONE;
510        entry.operations = 0L;
511
512        for (int i = 0; i < size; i++) {
513            // skip specific tags, since already counted in TAG_NONE
514            if (tag[i] != TAG_NONE) continue;
515
516            entry.iface = iface[i];
517            entry.rxBytes = rxBytes[i];
518            entry.rxPackets = rxPackets[i];
519            entry.txBytes = txBytes[i];
520            entry.txPackets = txPackets[i];
521            stats.combineValues(entry);
522        }
523
524        return stats;
525    }
526
527    /**
528     * Return total statistics grouped by {@link #uid}; doesn't mutate the
529     * original structure.
530     */
531    public NetworkStats groupedByUid() {
532        final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
533
534        final Entry entry = new Entry();
535        entry.iface = IFACE_ALL;
536        entry.set = SET_ALL;
537        entry.tag = TAG_NONE;
538
539        for (int i = 0; i < size; i++) {
540            // skip specific tags, since already counted in TAG_NONE
541            if (tag[i] != TAG_NONE) continue;
542
543            entry.uid = uid[i];
544            entry.rxBytes = rxBytes[i];
545            entry.rxPackets = rxPackets[i];
546            entry.txBytes = txBytes[i];
547            entry.txPackets = txPackets[i];
548            entry.operations = operations[i];
549            stats.combineValues(entry);
550        }
551
552        return stats;
553    }
554
555    /**
556     * Return all rows except those attributed to the requested UID; doesn't
557     * mutate the original structure.
558     */
559    public NetworkStats withoutUid(int uid) {
560        final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
561
562        Entry entry = new Entry();
563        for (int i = 0; i < size; i++) {
564            entry = getValues(i, entry);
565            if (entry.uid != uid) {
566                stats.addValues(entry);
567            }
568        }
569
570        return stats;
571    }
572
573    public void dump(String prefix, PrintWriter pw) {
574        pw.print(prefix);
575        pw.print("NetworkStats: elapsedRealtime="); pw.println(elapsedRealtime);
576        for (int i = 0; i < size; i++) {
577            pw.print(prefix);
578            pw.print("  iface="); pw.print(iface[i]);
579            pw.print(" uid="); pw.print(uid[i]);
580            pw.print(" set="); pw.print(setToString(set[i]));
581            pw.print(" tag="); pw.print(tagToString(tag[i]));
582            pw.print(" rxBytes="); pw.print(rxBytes[i]);
583            pw.print(" rxPackets="); pw.print(rxPackets[i]);
584            pw.print(" txBytes="); pw.print(txBytes[i]);
585            pw.print(" txPackets="); pw.print(txPackets[i]);
586            pw.print(" operations="); pw.println(operations[i]);
587        }
588    }
589
590    /**
591     * Return text description of {@link #set} value.
592     */
593    public static String setToString(int set) {
594        switch (set) {
595            case SET_ALL:
596                return "ALL";
597            case SET_DEFAULT:
598                return "DEFAULT";
599            case SET_FOREGROUND:
600                return "FOREGROUND";
601            default:
602                return "UNKNOWN";
603        }
604    }
605
606    /**
607     * Return text description of {@link #tag} value.
608     */
609    public static String tagToString(int tag) {
610        return "0x" + Integer.toHexString(tag);
611    }
612
613    @Override
614    public String toString() {
615        final CharArrayWriter writer = new CharArrayWriter();
616        dump("", new PrintWriter(writer));
617        return writer.toString();
618    }
619
620    /** {@inheritDoc} */
621    public int describeContents() {
622        return 0;
623    }
624
625    public static final Creator<NetworkStats> CREATOR = new Creator<NetworkStats>() {
626        public NetworkStats createFromParcel(Parcel in) {
627            return new NetworkStats(in);
628        }
629
630        public NetworkStats[] newArray(int size) {
631            return new NetworkStats[size];
632        }
633    };
634
635    public static class NonMonotonicException extends Exception {
636        public final NetworkStats left;
637        public final NetworkStats right;
638        public final int leftIndex;
639        public final int rightIndex;
640
641        public NonMonotonicException(
642                NetworkStats left, int leftIndex, NetworkStats right, int rightIndex) {
643            this.left = checkNotNull(left, "missing left");
644            this.right = checkNotNull(right, "missing right");
645            this.leftIndex = leftIndex;
646            this.rightIndex = rightIndex;
647        }
648    }
649}
650