diff options
6 files changed, 295 insertions, 16 deletions
diff --git a/core/java/android/net/NetworkStats.aidl b/core/java/android/net/NetworkStats.aidl new file mode 100644 index 0000000..d06ca65 --- /dev/null +++ b/core/java/android/net/NetworkStats.aidl @@ -0,0 +1,19 @@ +/** + * 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; + +parcelable NetworkStats; diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java new file mode 100644 index 0000000..4430e00 --- /dev/null +++ b/core/java/android/net/NetworkStats.java @@ -0,0 +1,158 @@ +/* + * 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 java.io.CharArrayWriter; +import java.io.PrintWriter; + +/** + * Collection of network statistics. Can contain summary details across all + * interfaces, or details with per-UID granularity. Designed to parcel quickly + * across process boundaries. + * + * @hide + */ +public class NetworkStats implements Parcelable { + /** {@link #iface} value when entry is summarized over all interfaces. */ + public static final String IFACE_ALL = null; + /** {@link #uid} value when entry is summarized over all UIDs. */ + public static final int UID_ALL = 0; + + /** + * {@link SystemClock#elapsedRealtime()} timestamp when this data was + * generated. + */ + public final long elapsedRealtime; + public final String[] iface; + public final int[] uid; + public final long[] rx; + public final long[] tx; + + // TODO: add fg/bg stats and tag granularity + + private NetworkStats(long elapsedRealtime, String[] iface, int[] uid, long[] rx, long[] tx) { + this.elapsedRealtime = elapsedRealtime; + this.iface = iface; + this.uid = uid; + this.rx = rx; + this.tx = tx; + } + + public NetworkStats(Parcel parcel) { + elapsedRealtime = parcel.readLong(); + iface = parcel.createStringArray(); + uid = parcel.createIntArray(); + rx = parcel.createLongArray(); + tx = parcel.createLongArray(); + } + + public static class Builder { + private long mElapsedRealtime; + private final String[] mIface; + private final int[] mUid; + private final long[] mRx; + private final long[] mTx; + + private int mIndex = 0; + + public Builder(long elapsedRealtime, int size) { + mElapsedRealtime = elapsedRealtime; + mIface = new String[size]; + mUid = new int[size]; + mRx = new long[size]; + mTx = new long[size]; + } + + public void addEntry(String iface, int uid, long rx, long tx) { + mIface[mIndex] = iface; + mUid[mIndex] = uid; + mRx[mIndex] = rx; + mTx[mIndex] = tx; + mIndex++; + } + + public NetworkStats build() { + if (mIndex != mIface.length) { + throw new IllegalArgumentException("unexpected number of entries"); + } + return new NetworkStats(mElapsedRealtime, mIface, mUid, mRx, mTx); + } + } + + /** + * Find first stats index that matches the requested parameters. + */ + public int findIndex(String iface, int uid) { + for (int i = 0; i < this.iface.length; i++) { + if (equal(iface, this.iface[i]) && uid == this.uid[i]) { + return i; + } + } + return -1; + } + + private static boolean equal(Object a, Object b) { + return a == b || (a != null && a.equals(b)); + } + + /** {@inheritDoc} */ + public int describeContents() { + return 0; + } + + 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 void writeToParcel(Parcel dest, int flags) { + dest.writeLong(elapsedRealtime); + dest.writeStringArray(iface); + dest.writeIntArray(uid); + dest.writeLongArray(rx); + dest.writeLongArray(tx); + } + + public static final Creator<NetworkStats> CREATOR = new Creator<NetworkStats>() { + public NetworkStats createFromParcel(Parcel in) { + return new NetworkStats(in); + } + + public NetworkStats[] newArray(int size) { + return new NetworkStats[size]; + } + }; +} diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl index 212c5fb..fe36786 100644 --- a/core/java/android/os/INetworkManagementService.aidl +++ b/core/java/android/os/INetworkManagementService.aidl @@ -19,6 +19,7 @@ package android.os; import android.net.InterfaceConfiguration; import android.net.INetworkManagementEventObserver; +import android.net.NetworkStats; import android.net.wifi.WifiConfiguration; /** @@ -65,7 +66,6 @@ interface INetworkManagementService ** TETHERING RELATED **/ - /** * Returns true if IP forwarding is enabled */ @@ -181,17 +181,23 @@ interface INetworkManagementService void setAccessPoint(in WifiConfiguration wifiConfig, String wlanIface, String softapIface); /** - * Read number of bytes sent over an interface + ** DATA USAGE RELATED + **/ + + /** + * Return global network statistics summarized at an interface level, + * without any UID-level granularity. */ - long getInterfaceTxCounter(String iface); + NetworkStats getNetworkStatsSummary(); /** - * Read number of bytes received over an interface + * Return detailed network statistics with UID-level granularity, + * including interface and tag details. */ - long getInterfaceRxCounter(String iface); + NetworkStats getNetworkStatsDetail(); /** - * Configures bandwidth throttling on an interface + * Configures bandwidth throttling on an interface. */ void setInterfaceThrottle(String iface, int rxKbps, int txKbps); diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java index 44f5df2..d931350 100644 --- a/services/java/com/android/server/NetworkManagementService.java +++ b/services/java/com/android/server/NetworkManagementService.java @@ -23,6 +23,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.res.Resources; import android.content.pm.PackageManager; +import android.net.NetworkStats; import android.net.Uri; import android.net.InterfaceConfiguration; import android.net.INetworkManagementEventObserver; @@ -32,6 +33,8 @@ import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiConfiguration.KeyMgmt; import android.os.INetworkManagementService; import android.os.Handler; +import android.os.RemoteException; +import android.os.SystemClock; import android.os.SystemProperties; import android.text.TextUtils; import android.util.Log; @@ -44,13 +47,21 @@ import android.content.ContentResolver; import android.database.ContentObserver; import java.io.File; +import java.io.FileInputStream; import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.RandomAccessFile; +import java.io.Reader; import java.lang.IllegalStateException; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.concurrent.CountDownLatch; +import libcore.io.IoUtils; + /** * @hide */ @@ -716,12 +727,47 @@ class NetworkManagementService extends INetworkManagementService.Stub { return -1; } - public long getInterfaceRxCounter(String iface) { - return getInterfaceCounter(iface, true); + /** {@inheritDoc} */ + public NetworkStats getNetworkStatsSummary() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); + + final String[] ifaces = listInterfaces(); + final NetworkStats.Builder stats = new NetworkStats.Builder( + SystemClock.elapsedRealtime(), ifaces.length); + + for (String iface : ifaces) { + final long rx = getInterfaceCounter(iface, true); + final long tx = getInterfaceCounter(iface, false); + stats.addEntry(iface, NetworkStats.UID_ALL, rx, tx); + } + + return stats.build(); } - public long getInterfaceTxCounter(String iface) { - return getInterfaceCounter(iface, false); + /** {@inheritDoc} */ + public NetworkStats getNetworkStatsDetail() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); + + final File procPath = new File("/proc/uid_stat"); + final String[] knownUids = procPath.list(); + final NetworkStats.Builder stats = new NetworkStats.Builder( + SystemClock.elapsedRealtime(), knownUids.length); + + // TODO: kernel module will provide interface-level stats in future + // TODO: migrate these stats to come across netd in bulk, instead of all + // these individual file reads. + for (String uid : knownUids) { + final File uidPath = new File(procPath, uid); + final int rx = readSingleIntFromFile(new File(uidPath, "tcp_rcv")); + final int tx = readSingleIntFromFile(new File(uidPath, "tcp_snd")); + + final int uidInt = Integer.parseInt(uid); + stats.addEntry(NetworkStats.IFACE_ALL, uidInt, rx, tx); + } + + return stats.build(); } public void setInterfaceThrottle(String iface, int rxKbps, int txKbps) { @@ -782,4 +828,24 @@ class NetworkManagementService extends INetworkManagementService.Stub { public int getInterfaceTxThrottle(String iface) { return getInterfaceThrottle(iface, false); } + + /** + * Utility method to read a single plain-text {@link Integer} from the given + * {@link File}, usually from a {@code /proc/} filesystem. + */ + private static int readSingleIntFromFile(File file) { + RandomAccessFile f = null; + try { + f = new RandomAccessFile(file, "r"); + byte[] buffer = new byte[(int) f.length()]; + f.readFully(buffer); + return Integer.parseInt(new String(buffer).trim()); + } catch (NumberFormatException e) { + return -1; + } catch (IOException e) { + return -1; + } finally { + IoUtils.closeQuietly(f); + } + } } diff --git a/services/java/com/android/server/ThrottleService.java b/services/java/com/android/server/ThrottleService.java index 02332b7..510ff62 100644 --- a/services/java/com/android/server/ThrottleService.java +++ b/services/java/com/android/server/ThrottleService.java @@ -33,6 +33,7 @@ import android.content.res.Resources; import android.database.ContentObserver; import android.net.INetworkManagementEventObserver; import android.net.IThrottleManager; +import android.net.NetworkStats; import android.net.ThrottleManager; import android.os.Binder; import android.os.Environment; @@ -531,8 +532,17 @@ public class ThrottleService extends IThrottleManager.Stub { long incRead = 0; long incWrite = 0; try { - incRead = mNMService.getInterfaceRxCounter(mIface) - mLastRead; - incWrite = mNMService.getInterfaceTxCounter(mIface) - mLastWrite; + final NetworkStats stats = mNMService.getNetworkStatsSummary(); + final int index = stats.findIndex(mIface, NetworkStats.UID_ALL); + + if (index != -1) { + incRead = stats.rx[index] - mLastRead; + incWrite = stats.tx[index] - mLastWrite; + } else { + // missing iface, assume stats are 0 + Slog.w(TAG, "unable to find stats for iface " + mIface); + } + // handle iface resets - on some device the 3g iface comes and goes and gets // totals reset to 0. Deal with it if ((incRead < 0) || (incWrite < 0)) { diff --git a/services/tests/servicestests/src/com/android/server/ThrottleServiceTest.java b/services/tests/servicestests/src/com/android/server/ThrottleServiceTest.java index 6f55f46..f20d5e5 100644 --- a/services/tests/servicestests/src/com/android/server/ThrottleServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/ThrottleServiceTest.java @@ -34,11 +34,17 @@ import android.content.ContextWrapper; import android.content.Intent; import android.content.IntentFilter; import android.net.INetworkManagementEventObserver; +import android.net.NetworkStats; import android.net.ThrottleManager; +import android.os.IBinder; import android.os.INetworkManagementService; +import android.os.ServiceManager; +import android.os.SystemClock; import android.provider.Settings; import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.Suppress; import android.text.format.DateUtils; +import android.util.Log; import android.util.TrustedTime; import java.util.Iterator; @@ -222,6 +228,16 @@ public class ThrottleServiceTest extends AndroidTestCase { verify(mMockTime, mMockNMService); } + @Suppress + public void testReturnStats() throws Exception { + final IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); + final INetworkManagementService nmService = INetworkManagementService.Stub.asInterface(b); + + // test is currently no-op, just exercises stats apis + Log.d(TAG, nmService.getNetworkStatsSummary().toString()); + Log.d(TAG, nmService.getNetworkStatsDetail().toString()); + } + /** * Persist the given {@link ThrottleService} policy into {@link Settings}. */ @@ -272,12 +288,16 @@ public class ThrottleServiceTest extends AndroidTestCase { } /** - * Expect {@link NetworkManagementService#getInterfaceRxCounter} mock calls, - * responding with the given counter values. + * Expect {@link NetworkManagementService#getNetworkStatsSummary()} mock + * calls, responding with the given counter values. */ public void expectGetInterfaceCounter(long rx, long tx) throws Exception { - expect(mMockNMService.getInterfaceRxCounter(isA(String.class))).andReturn(rx).atLeastOnce(); - expect(mMockNMService.getInterfaceTxCounter(isA(String.class))).andReturn(tx).atLeastOnce(); + // TODO: provide elapsedRealtime mock to match TimeAuthority + final NetworkStats.Builder stats = new NetworkStats.Builder( + SystemClock.elapsedRealtime(), 1); + stats.addEntry(TEST_IFACE, NetworkStats.UID_ALL, rx, tx); + + expect(mMockNMService.getNetworkStatsSummary()).andReturn(stats.build()).atLeastOnce(); } /** |