diff options
9 files changed, 275 insertions, 57 deletions
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 4c7d87f..a660bd7 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -1544,6 +1544,9 @@ public abstract class Context { */ public static final String NETWORKMANAGEMENT_SERVICE = "network_management"; + /** {@hide} */ + public static final String NETWORK_POLICY_SERVICE = "netpolicy"; + /** * Use with {@link #getSystemService} to retrieve a {@link * android.net.wifi.WifiManager} for handling management of diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java index 2312bd9..1913aa7 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -16,6 +16,7 @@ package android.net; +import android.content.Context; import android.os.RemoteException; /** @@ -43,6 +44,10 @@ public class NetworkPolicyManager { mService = service; } + public static NetworkPolicyManager getSystemService(Context context) { + return (NetworkPolicyManager) context.getSystemService(Context.NETWORK_POLICY_SERVICE); + } + /** * Set policy flags for specific UID. * diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java index 4430e00..0f207bc 100644 --- a/core/java/android/net/NetworkStats.java +++ b/core/java/android/net/NetworkStats.java @@ -36,6 +36,9 @@ public class NetworkStats implements Parcelable { /** {@link #uid} value when entry is summarized over all UIDs. */ public static final int UID_ALL = 0; + // 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. @@ -81,12 +84,13 @@ public class NetworkStats implements Parcelable { mTx = new long[size]; } - public void addEntry(String iface, int uid, long rx, long tx) { + public Builder addEntry(String iface, int uid, long rx, long tx) { mIface[mIndex] = iface; mUid[mIndex] = uid; mRx[mIndex] = rx; mTx[mIndex] = tx; mIndex++; + return this; } public NetworkStats build() { @@ -97,11 +101,17 @@ public class NetworkStats implements Parcelable { } } + public int length() { + // length is identical for all fields + return iface.length; + } + /** * 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++) { + final int length = length(); + for (int i = 0; i < length; i++) { if (equal(iface, this.iface[i]) && uid == this.uid[i]) { return i; } @@ -109,13 +119,38 @@ public class NetworkStats implements Parcelable { return -1; } - private static boolean equal(Object a, Object b) { - return a == b || (a != null && a.equals(b)); + /** + * 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. + */ + public NetworkStats subtract(NetworkStats value) { + // result will have our rows, but no meaningful timestamp + final int length = length(); + final NetworkStats.Builder result = new NetworkStats.Builder(-1, length); + + for (int i = 0; i < length; 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 + final long rx = this.rx[i] - value.rx[j]; + final long tx = this.tx[i] - value.tx[j]; + result.addEntry(iface, uid, rx, tx); + } + } + + return result.build(); } - /** {@inheritDoc} */ - public int describeContents() { - return 0; + private static boolean equal(Object a, Object b) { + return a == b || (a != null && a.equals(b)); } public void dump(String prefix, PrintWriter pw) { @@ -138,6 +173,11 @@ public class NetworkStats implements Parcelable { } /** {@inheritDoc} */ + public int describeContents() { + return 0; + } + + /** {@inheritDoc} */ public void writeToParcel(Parcel dest, int flags) { dest.writeLong(elapsedRealtime); dest.writeStringArray(iface); diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java index 7ee7a81..c0ff734 100644 --- a/core/java/android/net/TrafficStats.java +++ b/core/java/android/net/TrafficStats.java @@ -16,6 +16,12 @@ package android.net; +import android.content.Context; +import android.os.IBinder; +import android.os.INetworkManagementService; +import android.os.RemoteException; +import android.os.ServiceManager; + import dalvik.system.BlockGuard; import java.net.Socket; @@ -36,6 +42,17 @@ public class TrafficStats { public final static int UNSUPPORTED = -1; /** + * Snapshot of {@link NetworkStats} when the currently active profiling + * session started, or {@code null} if no session active. + * + * @see #startDataProfiling(Context) + * @see #stopDataProfiling(Context) + */ + private static NetworkStats sActiveProfilingStart; + + private static Object sProfilingLock = new Object(); + + /** * Set active tag to use when accounting {@link Socket} traffic originating * from the current thread. Only one active tag per thread is supported. * <p> @@ -93,6 +110,44 @@ public class TrafficStats { } /** + * Start profiling data usage for current UID. Only one profiling session + * can be active at a time. + * + * @hide + */ + public static void startDataProfiling(Context context) { + synchronized (sProfilingLock) { + if (sActiveProfilingStart != null) { + throw new IllegalStateException("already profiling data"); + } + + // take snapshot in time; we calculate delta later + sActiveProfilingStart = getNetworkStatsForUid(context); + } + } + + /** + * Stop profiling data usage for current UID. + * + * @return Detailed {@link NetworkStats} of data that occurred since last + * {@link #startDataProfiling(Context)} call. + * @hide + */ + public static NetworkStats stopDataProfiling(Context context) { + synchronized (sProfilingLock) { + if (sActiveProfilingStart == null) { + throw new IllegalStateException("not profiling data"); + } + + // subtract starting values and return delta + final NetworkStats profilingStop = getNetworkStatsForUid(context); + final NetworkStats profilingDelta = profilingStop.subtract(sActiveProfilingStart); + sActiveProfilingStart = null; + return profilingDelta; + } + } + + /** * Get the total number of packets transmitted through the mobile interface. * * @return number of packets. If the statistics are not supported by this device, @@ -350,4 +405,21 @@ public class TrafficStats { * {@link #UNSUPPORTED} will be returned. */ public static native long getUidUdpRxPackets(int uid); + + /** + * Return detailed {@link NetworkStats} for the current UID. Requires no + * special permission. + */ + private static NetworkStats getNetworkStatsForUid(Context context) { + final IBinder binder = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); + final INetworkManagementService service = INetworkManagementService.Stub.asInterface( + binder); + + final int uid = android.os.Process.myUid(); + try { + return service.getNetworkStatsUidDetail(uid); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } } diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl index ecc111b..f17a6f2 100644 --- a/core/java/android/os/INetworkManagementService.aidl +++ b/core/java/android/os/INetworkManagementService.aidl @@ -214,6 +214,12 @@ interface INetworkManagementService NetworkStats getNetworkStatsDetail(); /** + * Return detailed network statistics for the requested UID, + * including interface and tag details. + */ + NetworkStats getNetworkStatsUidDetail(int uid); + + /** * Configures bandwidth throttling on an interface. */ void setInterfaceThrottle(String iface, int rxKbps, int txKbps); diff --git a/core/tests/coretests/src/android/net/NetworkStatsTest.java b/core/tests/coretests/src/android/net/NetworkStatsTest.java new file mode 100644 index 0000000..45719c2 --- /dev/null +++ b/core/tests/coretests/src/android/net/NetworkStatsTest.java @@ -0,0 +1,97 @@ +/* + * 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.SystemClock; +import android.test.suitebuilder.annotation.SmallTest; + +import junit.framework.TestCase; + +@SmallTest +public class NetworkStatsTest extends TestCase { + + private static final String TEST_IFACE = "test0"; + + public void testFindIndex() throws Exception { + final NetworkStats stats = new NetworkStats.Builder(SystemClock.elapsedRealtime(), 3) + .addEntry(TEST_IFACE, 100, 1024, 0) + .addEntry(TEST_IFACE, 101, 0, 1024) + .addEntry(TEST_IFACE, 102, 1024, 1024).build(); + + assertEquals(2, stats.findIndex(TEST_IFACE, 102)); + assertEquals(2, stats.findIndex(TEST_IFACE, 102)); + assertEquals(0, stats.findIndex(TEST_IFACE, 100)); + assertEquals(-1, stats.findIndex(TEST_IFACE, 6)); + } + + public void testSubtractIdenticalData() throws Exception { + final NetworkStats before = new NetworkStats.Builder(SystemClock.elapsedRealtime(), 2) + .addEntry(TEST_IFACE, 100, 1024, 0) + .addEntry(TEST_IFACE, 101, 0, 1024).build(); + + final NetworkStats after = new NetworkStats.Builder(SystemClock.elapsedRealtime(), 2) + .addEntry(TEST_IFACE, 100, 1024, 0) + .addEntry(TEST_IFACE, 101, 0, 1024).build(); + + final NetworkStats result = after.subtract(before); + + assertEquals(0, result.rx[0]); + assertEquals(0, result.tx[0]); + assertEquals(0, result.rx[1]); + assertEquals(0, result.tx[1]); + } + + public void testSubtractIdenticalRows() throws Exception { + final NetworkStats before = new NetworkStats.Builder(SystemClock.elapsedRealtime(), 2) + .addEntry(TEST_IFACE, 100, 1024, 0) + .addEntry(TEST_IFACE, 101, 0, 1024).build(); + + final NetworkStats after = new NetworkStats.Builder(SystemClock.elapsedRealtime(), 2) + .addEntry(TEST_IFACE, 100, 1025, 2) + .addEntry(TEST_IFACE, 101, 3, 1028).build(); + + final NetworkStats result = after.subtract(before); + + // expect delta between measurements + assertEquals(1, result.rx[0]); + assertEquals(2, result.tx[0]); + assertEquals(3, result.rx[1]); + assertEquals(4, result.tx[1]); + } + + public void testSubtractNewRows() throws Exception { + final NetworkStats before = new NetworkStats.Builder(SystemClock.elapsedRealtime(), 2) + .addEntry(TEST_IFACE, 100, 1024, 0) + .addEntry(TEST_IFACE, 101, 0, 1024).build(); + + final NetworkStats after = new NetworkStats.Builder(SystemClock.elapsedRealtime(), 3) + .addEntry(TEST_IFACE, 100, 1024, 0) + .addEntry(TEST_IFACE, 101, 0, 1024) + .addEntry(TEST_IFACE, 102, 1024, 1024).build(); + + final NetworkStats result = after.subtract(before); + + // its okay to have new rows + assertEquals(0, result.rx[0]); + assertEquals(0, result.tx[0]); + assertEquals(0, result.rx[1]); + assertEquals(0, result.tx[1]); + assertEquals(1024, result.rx[2]); + assertEquals(1024, result.tx[2]); + } + +} diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java index 7c613c1..8f179f5 100644 --- a/services/java/com/android/server/NetworkManagementService.java +++ b/services/java/com/android/server/NetworkManagementService.java @@ -16,51 +16,34 @@ package com.android.server; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; import android.content.Context; -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; +import android.net.InterfaceConfiguration; import android.net.LinkAddress; +import android.net.NetworkStats; import android.net.NetworkUtils; import android.net.RouteInfo; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiConfiguration.KeyMgmt; +import android.os.Binder; 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; import android.util.Slog; -import java.util.ArrayList; -import java.util.NoSuchElementException; -import java.util.StringTokenizer; -import android.provider.Settings; -import android.content.ContentResolver; -import android.database.ContentObserver; import java.io.BufferedReader; import java.io.DataInputStream; 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.Inet4Address; -import java.net.UnknownHostException; +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.NoSuchElementException; +import java.util.StringTokenizer; import java.util.concurrent.CountDownLatch; import libcore.io.IoUtils; @@ -69,14 +52,16 @@ import libcore.io.IoUtils; * @hide */ class NetworkManagementService extends INetworkManagementService.Stub { - - private static final String TAG = "NetworkManagmentService"; + private static final String TAG = "NetworkManagementService"; private static final boolean DBG = false; private static final String NETD_TAG = "NetdConnector"; private static final int ADD = 1; private static final int REMOVE = 2; + /** Base path to UID-granularity network statistics. */ + private static final File PATH_PROC_UID_STAT = new File("/proc/uid_stat"); + class NetdResponseCode { public static final int InterfaceListResult = 110; public static final int TetherInterfaceListResult = 111; @@ -891,7 +876,7 @@ class NetworkManagementService extends INetworkManagementService.Stub { return -1; } - /** {@inheritDoc} */ + @Override public NetworkStats getNetworkStatsSummary() { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); @@ -909,31 +894,46 @@ class NetworkManagementService extends INetworkManagementService.Stub { return stats.build(); } - /** {@inheritDoc} */ + @Override 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 String[] knownUids = PATH_PROC_UID_STAT.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); + collectNetworkStatsDetail(stats, uidInt); } return stats.build(); } + @Override + public NetworkStats getNetworkStatsUidDetail(int uid) { + if (Binder.getCallingUid() != uid) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); + } + + final NetworkStats.Builder stats = new NetworkStats.Builder( + SystemClock.elapsedRealtime(), 1); + collectNetworkStatsDetail(stats, uid); + return stats.build(); + } + + private void collectNetworkStatsDetail(NetworkStats.Builder stats, int uid) { + // 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. + final File uidPath = new File(PATH_PROC_UID_STAT, Integer.toString(uid)); + final long rx = readSingleLongFromFile(new File(uidPath, "tcp_rcv")); + final long tx = readSingleLongFromFile(new File(uidPath, "tcp_snd")); + stats.addEntry(NetworkStats.IFACE_ALL, uid, rx, tx); + } + public void setInterfaceThrottle(String iface, int rxKbps, int txKbps) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); @@ -994,22 +994,17 @@ class NetworkManagementService extends INetworkManagementService.Stub { } /** - * Utility method to read a single plain-text {@link Integer} from the given + * Utility method to read a single plain-text {@link Long} from the given * {@link File}, usually from a {@code /proc/} filesystem. */ - private static int readSingleIntFromFile(File file) { - RandomAccessFile f = null; + private static long readSingleLongFromFile(File file) { try { - f = new RandomAccessFile(file, "r"); - byte[] buffer = new byte[(int) f.length()]; - f.readFully(buffer); - return Integer.parseInt(new String(buffer).trim()); + final byte[] buffer = IoUtils.readFileAsByteArray(file.toString()); + return Long.parseLong(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/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java index a7a4f07..312c32b 100644 --- a/services/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java @@ -38,8 +38,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private static final String TAG = "NetworkPolicy"; private static final boolean LOGD = true; - private static final String SERVICE_NAME = "netpolicy"; - private Context mContext; /** Current network policy for each UID. */ @@ -56,7 +54,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { public void publish(Context context) { mContext = context; - ServiceManager.addService(SERVICE_NAME, asBinder()); + ServiceManager.addService(Context.NETWORK_POLICY_SERVICE, asBinder()); mUidPolicy = new SparseIntArray(); mUidForeground = new SparseBooleanArray(); diff --git a/services/tests/servicestests/src/com/android/server/ThrottleServiceTest.java b/services/tests/servicestests/src/com/android/server/ThrottleServiceTest.java index f20d5e5..ca33d32 100644 --- a/services/tests/servicestests/src/com/android/server/ThrottleServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/ThrottleServiceTest.java @@ -42,6 +42,7 @@ import android.os.ServiceManager; import android.os.SystemClock; import android.provider.Settings; import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.LargeTest; import android.test.suitebuilder.annotation.Suppress; import android.text.format.DateUtils; import android.util.Log; @@ -54,6 +55,7 @@ import java.util.concurrent.Future; /** * Tests for {@link ThrottleService}. */ +@LargeTest public class ThrottleServiceTest extends AndroidTestCase { private static final String TAG = "ThrottleServiceTest"; |