NetworkStats.java revision 3f3913550c10792edb8aecf66cc83c3db5c8b311
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 java.io.CharArrayWriter;
25import java.io.PrintWriter;
26import java.util.HashSet;
27
28/**
29 * Collection of active network statistics. Can contain summary details across
30 * all interfaces, or details with per-UID granularity. Internally stores data
31 * as a large table, closely matching {@code /proc/} data format. This structure
32 * optimizes for rapid in-memory comparison, but consider using
33 * {@link NetworkStatsHistory} when persisting.
34 *
35 * @hide
36 */
37public class NetworkStats implements Parcelable {
38    /** {@link #iface} value when interface details unavailable. */
39    public static final String IFACE_ALL = null;
40    /** {@link #uid} value when UID details unavailable. */
41    public static final int UID_ALL = -1;
42
43    // NOTE: data should only be accounted for once in this structure; if data
44    // is broken out, the summarized version should not be included.
45
46    /**
47     * {@link SystemClock#elapsedRealtime()} timestamp when this data was
48     * generated.
49     */
50    public final long elapsedRealtime;
51    public final String[] iface;
52    public final int[] uid;
53    public final long[] rx;
54    public final long[] tx;
55
56    // TODO: add fg/bg stats once reported by kernel
57
58    private NetworkStats(long elapsedRealtime, String[] iface, int[] uid, long[] rx, long[] tx) {
59        this.elapsedRealtime = elapsedRealtime;
60        this.iface = iface;
61        this.uid = uid;
62        this.rx = rx;
63        this.tx = tx;
64    }
65
66    public NetworkStats(Parcel parcel) {
67        elapsedRealtime = parcel.readLong();
68        iface = parcel.createStringArray();
69        uid = parcel.createIntArray();
70        rx = parcel.createLongArray();
71        tx = parcel.createLongArray();
72    }
73
74    public static class Builder {
75        private long mElapsedRealtime;
76        private final String[] mIface;
77        private final int[] mUid;
78        private final long[] mRx;
79        private final long[] mTx;
80
81        private int mIndex = 0;
82
83        public Builder(long elapsedRealtime, int size) {
84            mElapsedRealtime = elapsedRealtime;
85            mIface = new String[size];
86            mUid = new int[size];
87            mRx = new long[size];
88            mTx = new long[size];
89        }
90
91        public Builder addEntry(String iface, int uid, long rx, long tx) {
92            mIface[mIndex] = iface;
93            mUid[mIndex] = uid;
94            mRx[mIndex] = rx;
95            mTx[mIndex] = tx;
96            mIndex++;
97            return this;
98        }
99
100        public NetworkStats build() {
101            if (mIndex != mIface.length) {
102                throw new IllegalArgumentException("unexpected number of entries");
103            }
104            return new NetworkStats(mElapsedRealtime, mIface, mUid, mRx, mTx);
105        }
106    }
107
108    public int length() {
109        // length is identical for all fields
110        return iface.length;
111    }
112
113    /**
114     * Find first stats index that matches the requested parameters.
115     */
116    public int findIndex(String iface, int uid) {
117        final int length = length();
118        for (int i = 0; i < length; i++) {
119            if (equal(iface, this.iface[i]) && uid == this.uid[i]) {
120                return i;
121            }
122        }
123        return -1;
124    }
125
126    /**
127     * Return list of unique interfaces known by this data structure.
128     */
129    public String[] getUniqueIfaces() {
130        final HashSet<String> ifaces = new HashSet<String>();
131        for (String iface : this.iface) {
132            if (iface != IFACE_ALL) {
133                ifaces.add(iface);
134            }
135        }
136        return ifaces.toArray(new String[ifaces.size()]);
137    }
138
139    /**
140     * Return list of unique UIDs known by this data structure.
141     */
142    public int[] getUniqueUids() {
143        final SparseBooleanArray uids = new SparseBooleanArray();
144        for (int uid : this.uid) {
145            uids.put(uid, true);
146        }
147
148        final int size = uids.size();
149        final int[] result = new int[size];
150        for (int i = 0; i < size; i++) {
151            result[i] = uids.keyAt(i);
152        }
153        return result;
154    }
155
156    /**
157     * Subtract the given {@link NetworkStats}, effectively leaving the delta
158     * between two snapshots in time. Assumes that statistics rows collect over
159     * time, and that none of them have disappeared.
160     *
161     * @throws IllegalArgumentException when given {@link NetworkStats} is
162     *             non-monotonic.
163     */
164    public NetworkStats subtract(NetworkStats value) {
165        return subtract(value, true, false);
166    }
167
168    /**
169     * Subtract the given {@link NetworkStats}, effectively leaving the delta
170     * between two snapshots in time. Assumes that statistics rows collect over
171     * time, and that none of them have disappeared.
172     * <p>
173     * Instead of throwing when counters are non-monotonic, this variant clamps
174     * results to never be negative.
175     */
176    public NetworkStats subtractClamped(NetworkStats value) {
177        return subtract(value, false, true);
178    }
179
180    /**
181     * Subtract the given {@link NetworkStats}, effectively leaving the delta
182     * between two snapshots in time. Assumes that statistics rows collect over
183     * time, and that none of them have disappeared.
184     *
185     * @param enforceMonotonic Validate that incoming value is strictly
186     *            monotonic compared to this object.
187     * @param clampNegative Instead of throwing like {@code enforceMonotonic},
188     *            clamp resulting counters at 0 to prevent negative values.
189     */
190    private NetworkStats subtract(
191            NetworkStats value, boolean enforceMonotonic, boolean clampNegative) {
192        final long deltaRealtime = this.elapsedRealtime - value.elapsedRealtime;
193        if (enforceMonotonic && deltaRealtime < 0) {
194            throw new IllegalArgumentException("found non-monotonic realtime");
195        }
196
197        // result will have our rows, and elapsed time between snapshots
198        final int length = length();
199        final NetworkStats.Builder result = new NetworkStats.Builder(deltaRealtime, length);
200        for (int i = 0; i < length; i++) {
201            final String iface = this.iface[i];
202            final int uid = this.uid[i];
203
204            // find remote row that matches, and subtract
205            final int j = value.findIndex(iface, uid);
206            if (j == -1) {
207                // newly appearing row, return entire value
208                result.addEntry(iface, uid, this.rx[i], this.tx[i]);
209            } else {
210                // existing row, subtract remote value
211                long rx = this.rx[i] - value.rx[j];
212                long tx = this.tx[i] - value.tx[j];
213                if (enforceMonotonic && (rx < 0 || tx < 0)) {
214                    throw new IllegalArgumentException("found non-monotonic values");
215                }
216                if (clampNegative) {
217                    rx = Math.max(0, rx);
218                    tx = Math.max(0, tx);
219                }
220                result.addEntry(iface, uid, rx, tx);
221            }
222        }
223
224        return result.build();
225    }
226
227    private static boolean equal(Object a, Object b) {
228        return a == b || (a != null && a.equals(b));
229    }
230
231    public void dump(String prefix, PrintWriter pw) {
232        pw.print(prefix);
233        pw.print("NetworkStats: elapsedRealtime="); pw.println(elapsedRealtime);
234        for (int i = 0; i < iface.length; i++) {
235            pw.print(prefix);
236            pw.print("  iface="); pw.print(iface[i]);
237            pw.print(" uid="); pw.print(uid[i]);
238            pw.print(" rx="); pw.print(rx[i]);
239            pw.print(" tx="); pw.println(tx[i]);
240        }
241    }
242
243    @Override
244    public String toString() {
245        final CharArrayWriter writer = new CharArrayWriter();
246        dump("", new PrintWriter(writer));
247        return writer.toString();
248    }
249
250    /** {@inheritDoc} */
251    public int describeContents() {
252        return 0;
253    }
254
255    /** {@inheritDoc} */
256    public void writeToParcel(Parcel dest, int flags) {
257        dest.writeLong(elapsedRealtime);
258        dest.writeStringArray(iface);
259        dest.writeIntArray(uid);
260        dest.writeLongArray(rx);
261        dest.writeLongArray(tx);
262    }
263
264    public static final Creator<NetworkStats> CREATOR = new Creator<NetworkStats>() {
265        public NetworkStats createFromParcel(Parcel in) {
266            return new NetworkStats(in);
267        }
268
269        public NetworkStats[] newArray(int size) {
270            return new NetworkStats[size];
271        }
272    };
273}
274