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