• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.net;
18 
19 import android.os.Parcel;
20 import android.os.Parcelable;
21 import android.os.SystemClock;
22 import android.util.Log;
23 import android.util.SparseBooleanArray;
24 
25 import com.android.internal.util.Objects;
26 
27 import java.io.CharArrayWriter;
28 import java.io.PrintWriter;
29 import java.util.Arrays;
30 import 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  */
41 public 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 
Entry()84         public Entry() {
85             this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
86         }
87 
Entry(long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)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 
Entry(String iface, int uid, int set, int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)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
toString()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 
NetworkStats(long elapsedRealtime, int initialSize)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 
NetworkStats(Parcel parcel)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} */
writeToParcel(Parcel dest, int flags)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
addIfaceValues( String iface, long rxBytes, long rxPackets, long txBytes, long txPackets)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
addValues(String iface, int uid, int set, int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)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      */
addValues(Entry entry)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      */
getValues(int i, Entry recycle)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 
getElapsedRealtime()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      */
getElapsedRealtimeAge()236     public long getElapsedRealtimeAge() {
237         return SystemClock.elapsedRealtime() - elapsedRealtime;
238     }
239 
size()240     public int size() {
241         return size;
242     }
243 
244     // @VisibleForTesting
internalSize()245     public int internalSize() {
246         return iface.length;
247     }
248 
249     @Deprecated
combineValues(String iface, int uid, int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)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 
combineValues(String iface, int uid, int set, int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)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      */
combineValues(Entry entry)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      */
combineAllValues(NetworkStats another)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      */
findIndex(String iface, int uid, int set, int tag)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      */
spliceOperationsFrom(NetworkStats stats)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      */
getUniqueIfaces()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      */
getUniqueUids()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      */
getTotalBytes()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      */
getTotal(Entry recycle)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      */
getTotal(Entry recycle, int limitUid)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      */
getTotal(Entry recycle, HashSet<String> limitIface)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      */
getTotal(Entry recycle, HashSet<String> limitIface, int limitUid)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      */
subtract(NetworkStats value)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      */
subtractClamped(NetworkStats value)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      */
subtract( NetworkStats value, boolean enforceMonotonic, boolean clampNegative)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      */
groupedByIface()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      */
groupedByUid()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 
dump(String prefix, PrintWriter pw)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      */
setToString(int set)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      */
tagToString(int tag)603     public static String tagToString(int tag) {
604         return "0x" + Integer.toHexString(tag);
605     }
606 
607     @Override
toString()608     public String toString() {
609         final CharArrayWriter writer = new CharArrayWriter();
610         dump("", new PrintWriter(writer));
611         return writer.toString();
612     }
613 
614     /** {@inheritDoc} */
describeContents()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