/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.net; import android.os.Parcel; import android.os.Parcelable; import android.os.SystemClock; import android.util.SparseBooleanArray; import java.io.CharArrayWriter; import java.io.PrintWriter; import java.util.Arrays; import java.util.HashSet; /** * Collection of active network statistics. Can contain summary details across * all interfaces, or details with per-UID granularity. Internally stores data * as a large table, closely matching {@code /proc/} data format. This structure * optimizes for rapid in-memory comparison, but consider using * {@link NetworkStatsHistory} when persisting. * * @hide */ public class NetworkStats implements Parcelable { /** {@link #iface} value when interface details unavailable. */ public static final String IFACE_ALL = null; /** {@link #uid} value when UID details unavailable. */ public static final int UID_ALL = -1; // NOTE: data should only be accounted for once in this structure; if data // is broken out, the summarized version should not be included. /** * {@link SystemClock#elapsedRealtime()} timestamp when this data was * generated. */ public final long elapsedRealtime; public int size; public String[] iface; public int[] uid; public long[] rx; public long[] tx; // TODO: add fg/bg stats once reported by kernel public NetworkStats(long elapsedRealtime, int initialSize) { this.elapsedRealtime = elapsedRealtime; this.size = 0; this.iface = new String[initialSize]; this.uid = new int[initialSize]; this.rx = new long[initialSize]; this.tx = new long[initialSize]; } public NetworkStats(Parcel parcel) { elapsedRealtime = parcel.readLong(); size = parcel.readInt(); iface = parcel.createStringArray(); uid = parcel.createIntArray(); rx = parcel.createLongArray(); tx = parcel.createLongArray(); } public NetworkStats addEntry(String iface, int uid, long rx, long tx) { if (size >= this.iface.length) { final int newLength = Math.max(this.iface.length, 10) * 3 / 2; this.iface = Arrays.copyOf(this.iface, newLength); this.uid = Arrays.copyOf(this.uid, newLength); this.rx = Arrays.copyOf(this.rx, newLength); this.tx = Arrays.copyOf(this.tx, newLength); } this.iface[size] = iface; this.uid[size] = uid; this.rx[size] = rx; this.tx[size] = tx; size++; return this; } @Deprecated public int length() { return size; } /** * Find first stats index that matches the requested parameters. */ public int findIndex(String iface, int uid) { for (int i = 0; i < size; i++) { if (equal(iface, this.iface[i]) && uid == this.uid[i]) { return i; } } return -1; } /** * Return list of unique interfaces known by this data structure. */ public String[] getUniqueIfaces() { final HashSet ifaces = new HashSet(); for (String iface : this.iface) { if (iface != IFACE_ALL) { ifaces.add(iface); } } return ifaces.toArray(new String[ifaces.size()]); } /** * Return list of unique UIDs known by this data structure. */ public int[] getUniqueUids() { final SparseBooleanArray uids = new SparseBooleanArray(); for (int uid : this.uid) { uids.put(uid, true); } final int size = uids.size(); final int[] result = new int[size]; for (int i = 0; i < size; i++) { result[i] = uids.keyAt(i); } return result; } /** * Subtract the given {@link NetworkStats}, effectively leaving the delta * between two snapshots in time. Assumes that statistics rows collect over * time, and that none of them have disappeared. * * @throws IllegalArgumentException when given {@link NetworkStats} is * non-monotonic. */ public NetworkStats subtract(NetworkStats value) { return subtract(value, true, false); } /** * Subtract the given {@link NetworkStats}, effectively leaving the delta * between two snapshots in time. Assumes that statistics rows collect over * time, and that none of them have disappeared. *

* Instead of throwing when counters are non-monotonic, this variant clamps * results to never be negative. */ public NetworkStats subtractClamped(NetworkStats value) { return subtract(value, false, true); } /** * Subtract the given {@link NetworkStats}, effectively leaving the delta * between two snapshots in time. Assumes that statistics rows collect over * time, and that none of them have disappeared. * * @param enforceMonotonic Validate that incoming value is strictly * monotonic compared to this object. * @param clampNegative Instead of throwing like {@code enforceMonotonic}, * clamp resulting counters at 0 to prevent negative values. */ private NetworkStats subtract( NetworkStats value, boolean enforceMonotonic, boolean clampNegative) { final long deltaRealtime = this.elapsedRealtime - value.elapsedRealtime; if (enforceMonotonic && deltaRealtime < 0) { throw new IllegalArgumentException("found non-monotonic realtime"); } // result will have our rows, and elapsed time between snapshots final NetworkStats result = new NetworkStats(deltaRealtime, size); for (int i = 0; i < size; i++) { final String iface = this.iface[i]; final int uid = this.uid[i]; // find remote row that matches, and subtract final int j = value.findIndex(iface, uid); if (j == -1) { // newly appearing row, return entire value result.addEntry(iface, uid, this.rx[i], this.tx[i]); } else { // existing row, subtract remote value long rx = this.rx[i] - value.rx[j]; long tx = this.tx[i] - value.tx[j]; if (enforceMonotonic && (rx < 0 || tx < 0)) { throw new IllegalArgumentException("found non-monotonic values"); } if (clampNegative) { rx = Math.max(0, rx); tx = Math.max(0, tx); } result.addEntry(iface, uid, rx, tx); } } return result; } private static boolean equal(Object a, Object b) { return a == b || (a != null && a.equals(b)); } public void dump(String prefix, PrintWriter pw) { pw.print(prefix); pw.print("NetworkStats: elapsedRealtime="); pw.println(elapsedRealtime); for (int i = 0; i < iface.length; i++) { pw.print(prefix); pw.print(" iface="); pw.print(iface[i]); pw.print(" uid="); pw.print(uid[i]); pw.print(" rx="); pw.print(rx[i]); pw.print(" tx="); pw.println(tx[i]); } } @Override public String toString() { final CharArrayWriter writer = new CharArrayWriter(); dump("", new PrintWriter(writer)); return writer.toString(); } /** {@inheritDoc} */ public int describeContents() { return 0; } /** {@inheritDoc} */ public void writeToParcel(Parcel dest, int flags) { dest.writeLong(elapsedRealtime); dest.writeInt(size); dest.writeStringArray(iface); dest.writeIntArray(uid); dest.writeLongArray(rx); dest.writeLongArray(tx); } public static final Creator CREATOR = new Creator() { public NetworkStats createFromParcel(Parcel in) { return new NetworkStats(in); } public NetworkStats[] newArray(int size) { return new NetworkStats[size]; } }; }