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