summaryrefslogtreecommitdiffstats
path: root/services
diff options
context:
space:
mode:
authorJeff Sharkey <jsharkey@android.com>2011-05-24 18:39:45 -0700
committerJeff Sharkey <jsharkey@android.com>2011-06-01 17:44:52 -0700
commit75279904202357565cf5a1cb11148d01f42b4569 (patch)
treedb3b40af4fdfda1d46d1d4c9e471bf4630656036 /services
parent77c1cc0aa4d088f54c3b36a05a19acfa5295c4da (diff)
downloadframeworks_base-75279904202357565cf5a1cb11148d01f42b4569.zip
frameworks_base-75279904202357565cf5a1cb11148d01f42b4569.tar.gz
frameworks_base-75279904202357565cf5a1cb11148d01f42b4569.tar.bz2
Collect historical network stats.
Periodically records delta network traffic into historical buckets to support other services, such NetworkPolicyManager and Settings UI. Introduces NetworkStatsHistory structure which contains sparse, uniform buckets of data usage defined by timestamps. Service periodically polls NetworkStats and records changes into buckets. It only persists to disk when substantial changes have occured. Current parameters create 4 buckets each day, and persist for 90 days, resulting in about 8kB of data per network. Only records stats for "well known" network interfaces that have been claimed by Telephony or Wi-Fi subsystems. Historical stats are also keyed off identity (such as IMSI) to support SIM swapping. Change-Id: Ia27d1289556a2bf9545fbc4f3b789425a01be53a
Diffstat (limited to 'services')
-rw-r--r--services/java/com/android/server/NetStatService.java96
-rw-r--r--services/java/com/android/server/SystemServer.java32
-rw-r--r--services/java/com/android/server/net/NetworkPolicyManagerService.java47
-rw-r--r--services/java/com/android/server/net/NetworkStatsService.java403
-rw-r--r--services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java12
5 files changed, 448 insertions, 142 deletions
diff --git a/services/java/com/android/server/NetStatService.java b/services/java/com/android/server/NetStatService.java
deleted file mode 100644
index 7fe6743..0000000
--- a/services/java/com/android/server/NetStatService.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2008 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 com.android.server;
-
-import android.content.Context;
-import android.net.TrafficStats;
-import android.os.INetStatService;
-import android.os.SystemClock;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
-public class NetStatService extends INetStatService.Stub {
- private final Context mContext;
-
- public NetStatService(Context context) {
- mContext = context;
- }
-
- public long getMobileTxPackets() {
- return TrafficStats.getMobileTxPackets();
- }
-
- public long getMobileRxPackets() {
- return TrafficStats.getMobileRxPackets();
- }
-
- public long getMobileTxBytes() {
- return TrafficStats.getMobileTxBytes();
- }
-
- public long getMobileRxBytes() {
- return TrafficStats.getMobileRxBytes();
- }
-
- public long getTotalTxPackets() {
- return TrafficStats.getTotalTxPackets();
- }
-
- public long getTotalRxPackets() {
- return TrafficStats.getTotalRxPackets();
- }
-
- public long getTotalTxBytes() {
- return TrafficStats.getTotalTxBytes();
- }
-
- public long getTotalRxBytes() {
- return TrafficStats.getTotalRxBytes();
- }
-
- @Override
- protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- // This data is accessible to any app -- no permission check needed.
-
- pw.print("Elapsed: total=");
- pw.print(SystemClock.elapsedRealtime());
- pw.print("ms awake=");
- pw.print(SystemClock.uptimeMillis());
- pw.println("ms");
-
- pw.print("Mobile: Tx=");
- pw.print(getMobileTxBytes());
- pw.print("B/");
- pw.print(getMobileTxPackets());
- pw.print("Pkts Rx=");
- pw.print(getMobileRxBytes());
- pw.print("B/");
- pw.print(getMobileRxPackets());
- pw.println("Pkts");
-
- pw.print("Total: Tx=");
- pw.print(getTotalTxBytes());
- pw.print("B/");
- pw.print(getTotalTxPackets());
- pw.print("Pkts Rx=");
- pw.print(getTotalRxBytes());
- pw.print("B/");
- pw.print(getTotalRxPackets());
- pw.println("Pkts");
- }
-}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 4cd601f..596cbac 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -49,6 +49,7 @@ import com.android.internal.os.SamplingProfilerIntegration;
import com.android.server.accessibility.AccessibilityManagerService;
import com.android.server.am.ActivityManagerService;
import com.android.server.net.NetworkPolicyManagerService;
+import com.android.server.net.NetworkStatsService;
import com.android.server.pm.PackageManagerService;
import com.android.server.usb.UsbService;
import com.android.server.wm.WindowManagerService;
@@ -116,7 +117,9 @@ class ServerThread extends Thread {
LightsService lights = null;
PowerManagerService power = null;
BatteryService battery = null;
+ AlarmManagerService alarm = null;
NetworkManagementService networkManagement = null;
+ NetworkStatsService networkStats = null;
NetworkPolicyManagerService networkPolicy = null;
ConnectivityService connectivity = null;
IPackageManager pm = null;
@@ -188,7 +191,7 @@ class ServerThread extends Thread {
power.init(context, lights, ActivityManagerService.getDefault(), battery);
Slog.i(TAG, "Alarm Manager");
- AlarmManagerService alarm = new AlarmManagerService(context);
+ alarm = new AlarmManagerService(context);
ServiceManager.addService(Context.ALARM_SERVICE, alarm);
Slog.i(TAG, "Init Watchdog");
@@ -274,27 +277,28 @@ class ServerThread extends Thread {
}
try {
- Slog.i(TAG, "NetStat Service");
- ServiceManager.addService("netstat", new NetStatService(context));
+ Slog.i(TAG, "NetworkManagement Service");
+ networkManagement = NetworkManagementService.create(context);
+ ServiceManager.addService(Context.NETWORKMANAGEMENT_SERVICE, networkManagement);
} catch (Throwable e) {
- Slog.e(TAG, "Failure starting NetStat Service", e);
+ Slog.e(TAG, "Failure starting NetworkManagement Service", e);
}
try {
- Slog.i(TAG, "NetworkPolicy Service");
- networkPolicy = new NetworkPolicyManagerService(
- context, ActivityManagerService.self(), power);
- ServiceManager.addService(Context.NETWORK_POLICY_SERVICE, networkPolicy);
+ Slog.i(TAG, "NetworkStats Service");
+ networkStats = new NetworkStatsService(context, networkManagement, alarm);
+ ServiceManager.addService(Context.NETWORK_STATS_SERVICE, networkStats);
} catch (Throwable e) {
- Slog.e(TAG, "Failure starting Connectivity Service", e);
+ Slog.e(TAG, "Failure starting NetworkStats Service", e);
}
try {
- Slog.i(TAG, "NetworkManagement Service");
- networkManagement = NetworkManagementService.create(context);
- ServiceManager.addService(Context.NETWORKMANAGEMENT_SERVICE, networkManagement);
+ Slog.i(TAG, "NetworkPolicy Service");
+ networkPolicy = new NetworkPolicyManagerService(
+ context, ActivityManagerService.self(), power, networkStats);
+ ServiceManager.addService(Context.NETWORK_POLICY_SERVICE, networkPolicy);
} catch (Throwable e) {
- Slog.e(TAG, "Failure starting NetworkManagement Service", e);
+ Slog.e(TAG, "Failure starting NetworkPolicy Service", e);
}
try {
@@ -535,6 +539,7 @@ class ServerThread extends Thread {
// These are needed to propagate to the runnable below.
final Context contextF = context;
final BatteryService batteryF = battery;
+ final NetworkStatsService networkStatsF = networkStats;
final NetworkPolicyManagerService networkPolicyF = networkPolicy;
final ConnectivityService connectivityF = connectivity;
final DockObserver dockF = dock;
@@ -561,6 +566,7 @@ class ServerThread extends Thread {
startSystemUi(contextF);
if (batteryF != null) batteryF.systemReady();
+ if (networkStatsF != null) networkStatsF.systemReady();
if (networkPolicyF != null) networkPolicyF.systemReady();
if (connectivityF != null) connectivityF.systemReady();
if (dockF != null) dockF.systemReady();
diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java
index 1ae8284..17c7161 100644
--- a/services/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -35,10 +35,10 @@ import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.INetworkPolicyListener;
import android.net.INetworkPolicyManager;
+import android.net.INetworkStatsService;
import android.os.IPowerManager;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
-import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -59,11 +59,12 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
private static final String TAG = "NetworkPolicy";
private static final boolean LOGD = true;
- private Context mContext;
- private IActivityManager mActivityManager;
- private IPowerManager mPowerManager;
+ private final Context mContext;
+ private final IActivityManager mActivityManager;
+ private final IPowerManager mPowerManager;
+ private final INetworkStatsService mNetworkStats;
- private Object mRulesLock = new Object();
+ private final Object mRulesLock = new Object();
private boolean mScreenOn;
@@ -80,21 +81,24 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
private final RemoteCallbackList<INetworkPolicyListener> mListeners = new RemoteCallbackList<
INetworkPolicyListener>();
- // TODO: periodically poll network stats and write to disk
// TODO: save/restore policy information from disk
// TODO: keep whitelist of system-critical services that should never have
// rules enforced, such as system, phone, and radio UIDs.
- public NetworkPolicyManagerService(
- Context context, IActivityManager activityManager, IPowerManager powerManager) {
+ // TODO: keep record of billing cycle details, and limit rules
+ // TODO: keep map of interfaces-to-billing-relationship
+
+ public NetworkPolicyManagerService(Context context, IActivityManager activityManager,
+ IPowerManager powerManager, INetworkStatsService networkStats) {
mContext = checkNotNull(context, "missing context");
mActivityManager = checkNotNull(activityManager, "missing activityManager");
mPowerManager = checkNotNull(powerManager, "missing powerManager");
+ mNetworkStats = checkNotNull(networkStats, "missing networkStats");
}
public void systemReady() {
- // TODO: read current policy+stats from disk and generate NMS rules
+ // TODO: read current policy from disk
updateScreenOn();
@@ -114,18 +118,13 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
screenFilter.addAction(Intent.ACTION_SCREEN_OFF);
mContext.registerReceiver(mScreenReceiver, screenFilter);
- final IntentFilter shutdownFilter = new IntentFilter();
- shutdownFilter.addAction(Intent.ACTION_SHUTDOWN);
- mContext.registerReceiver(mShutdownReceiver, shutdownFilter);
-
}
private IProcessObserver mProcessObserver = new IProcessObserver.Stub() {
@Override
public void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) {
// only someone like AMS should only be calling us
- mContext.enforceCallingOrSelfPermission(
- MANAGE_APP_TOKENS, "requires MANAGE_APP_TOKENS permission");
+ mContext.enforceCallingOrSelfPermission(MANAGE_APP_TOKENS, TAG);
synchronized (mRulesLock) {
// because a uid can have multiple pids running inside, we need to
@@ -145,8 +144,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
@Override
public void onProcessDied(int pid, int uid) {
// only someone like AMS should only be calling us
- mContext.enforceCallingOrSelfPermission(
- MANAGE_APP_TOKENS, "requires MANAGE_APP_TOKENS permission");
+ mContext.enforceCallingOrSelfPermission(MANAGE_APP_TOKENS, TAG);
synchronized (mRulesLock) {
// clear records and recompute, when they exist
@@ -170,19 +168,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
};
- private BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- // TODO: persist any pending stats during clean shutdown
- Log.d(TAG, "persisting stats");
- }
- };
-
@Override
public void setUidPolicy(int uid, int policy) {
// TODO: create permission for modifying data policy
- mContext.enforceCallingOrSelfPermission(
- UPDATE_DEVICE_STATS, "requires UPDATE_DEVICE_STATS permission");
+ mContext.enforceCallingOrSelfPermission(UPDATE_DEVICE_STATS, TAG);
final int oldPolicy;
synchronized (mRulesLock) {
@@ -228,7 +217,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
@Override
protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
- mContext.enforceCallingOrSelfPermission(DUMP, "requires DUMP permission");
+ mContext.enforceCallingOrSelfPermission(DUMP, TAG);
synchronized (mRulesLock) {
fout.println("Policy status for known UIDs:");
@@ -366,7 +355,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
return value;
}
-
+
private static void collectKeys(SparseIntArray source, SparseBooleanArray target) {
final int size = source.size();
for (int i = 0; i < size; i++) {
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
new file mode 100644
index 0000000..d9c1f25
--- /dev/null
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -0,0 +1,403 @@
+/*
+ * Copyright (C) 2008 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 com.android.server.net;
+
+import static android.Manifest.permission.DUMP;
+import static android.Manifest.permission.SHUTDOWN;
+import static android.Manifest.permission.UPDATE_DEVICE_STATS;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.NetworkStats.UID_ALL;
+
+import android.app.AlarmManager;
+import android.app.IAlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.INetworkStatsService;
+import android.net.LinkProperties;
+import android.net.NetworkStats;
+import android.net.NetworkStatsHistory;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.INetworkManagementService;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.telephony.TelephonyManager;
+import android.text.format.DateUtils;
+import android.util.NtpTrustedTime;
+import android.util.Slog;
+import android.util.TrustedTime;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.TelephonyIntents;
+import com.google.android.collect.Maps;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.HashMap;
+
+/**
+ * Collect and persist detailed network statistics, and provide this data to
+ * other system services.
+ */
+public class NetworkStatsService extends INetworkStatsService.Stub {
+ private static final String TAG = "NetworkStatsService";
+ private static final boolean LOGD = true;
+
+ private final Context mContext;
+ private final INetworkManagementService mNetworkManager;
+ private final IAlarmManager mAlarmManager;
+ private final TrustedTime mTime;
+
+ private static final String ACTION_NETWORK_STATS_POLL =
+ "com.android.server.action.NETWORK_STATS_POLL";
+
+ private PendingIntent mPollIntent;
+
+ // TODO: move tweakable params to Settings.Secure
+ // TODO: listen for kernel push events through netd instead of polling
+
+ private static final long KB_IN_BYTES = 1024;
+
+ private static final long POLL_INTERVAL = AlarmManager.INTERVAL_FIFTEEN_MINUTES;
+ private static final long SUMMARY_BUCKET_DURATION = 6 * DateUtils.HOUR_IN_MILLIS;
+ private static final long SUMMARY_MAX_HISTORY = 90 * DateUtils.DAY_IN_MILLIS;
+
+ // TODO: remove these high-frequency testing values
+// private static final long POLL_INTERVAL = 5 * DateUtils.SECOND_IN_MILLIS;
+// private static final long SUMMARY_BUCKET_DURATION = 10 * DateUtils.SECOND_IN_MILLIS;
+// private static final long SUMMARY_MAX_HISTORY = 2 * DateUtils.MINUTE_IN_MILLIS;
+
+ /** Minimum delta required to persist to disk. */
+ private static final long SUMMARY_PERSIST_THRESHOLD = 64 * KB_IN_BYTES;
+
+ private static final long TIME_CACHE_MAX_AGE = DateUtils.DAY_IN_MILLIS;
+
+ private final Object mStatsLock = new Object();
+
+ /** Set of active ifaces during this boot. */
+ private HashMap<String, InterfaceInfo> mActiveIface = Maps.newHashMap();
+ /** Set of historical stats for known ifaces. */
+ private HashMap<InterfaceInfo, NetworkStatsHistory> mIfaceStats = Maps.newHashMap();
+
+ private NetworkStats mLastPollStats;
+ private NetworkStats mLastPersistStats;
+
+ private final HandlerThread mHandlerThread;
+ private final Handler mHandler;
+
+ // TODO: collect detailed uid stats, storing tag-granularity data until next
+ // dropbox, and uid summary for a specific bucket count.
+
+ // TODO: periodically compile statistics and send to dropbox.
+
+ public NetworkStatsService(
+ Context context, INetworkManagementService networkManager, IAlarmManager alarmManager) {
+ // TODO: move to using cached NtpTrustedTime
+ this(context, networkManager, alarmManager, new NtpTrustedTime());
+ }
+
+ public NetworkStatsService(Context context, INetworkManagementService networkManager,
+ IAlarmManager alarmManager, TrustedTime time) {
+ mContext = checkNotNull(context, "missing Context");
+ mNetworkManager = checkNotNull(networkManager, "missing INetworkManagementService");
+ mAlarmManager = checkNotNull(alarmManager, "missing IAlarmManager");
+ mTime = checkNotNull(time, "missing TrustedTime");
+
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+ }
+
+ public void systemReady() {
+ // read historical stats from disk
+ readStatsLocked();
+
+ // watch other system services that claim interfaces
+ // TODO: protect incoming broadcast with permissions check.
+ // TODO: consider migrating this to ConnectivityService, but it might
+ // cause a circular dependency.
+ final IntentFilter interfaceFilter = new IntentFilter();
+ interfaceFilter.addAction(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
+ interfaceFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+ mContext.registerReceiver(mInterfaceReceiver, interfaceFilter);
+
+ // listen for periodic polling events
+ final IntentFilter pollFilter = new IntentFilter(ACTION_NETWORK_STATS_POLL);
+ mContext.registerReceiver(mPollReceiver, pollFilter, UPDATE_DEVICE_STATS, mHandler);
+
+ // persist stats during clean shutdown
+ final IntentFilter shutdownFilter = new IntentFilter(Intent.ACTION_SHUTDOWN);
+ mContext.registerReceiver(mShutdownReceiver, shutdownFilter, SHUTDOWN, null);
+
+ try {
+ registerPollAlarmLocked();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "unable to register poll alarm");
+ }
+ }
+
+ /**
+ * Clear any existing {@link #ACTION_NETWORK_STATS_POLL} alarms, and
+ * reschedule based on current {@link #POLL_INTERVAL} value.
+ */
+ private void registerPollAlarmLocked() throws RemoteException {
+ if (mPollIntent != null) {
+ mAlarmManager.remove(mPollIntent);
+ }
+
+ mPollIntent = PendingIntent.getBroadcast(
+ mContext, 0, new Intent(ACTION_NETWORK_STATS_POLL), 0);
+
+ final long currentRealtime = SystemClock.elapsedRealtime();
+ mAlarmManager.setInexactRepeating(
+ AlarmManager.ELAPSED_REALTIME, currentRealtime, POLL_INTERVAL, mPollIntent);
+ }
+
+ @Override
+ public NetworkStatsHistory[] getNetworkStatsSummary(int networkType) {
+ // TODO: return history for requested types
+ return null;
+ }
+
+ @Override
+ public NetworkStatsHistory getNetworkStatsUid(int uid) {
+ // TODO: return history for requested uid
+ return null;
+ }
+
+ /**
+ * Receiver that watches for other system components that claim network
+ * interfaces. Used to associate {@link TelephonyManager#getSubscriberId()}
+ * with mobile interfaces.
+ */
+ private BroadcastReceiver mInterfaceReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED.equals(action)) {
+ final LinkProperties prop = intent.getParcelableExtra(
+ Phone.DATA_LINK_PROPERTIES_KEY);
+ final String iface = prop != null ? prop.getInterfaceName() : null;
+ if (iface != null) {
+ final TelephonyManager teleManager = (TelephonyManager) context
+ .getSystemService(Context.TELEPHONY_SERVICE);
+ final InterfaceInfo info = new InterfaceInfo(
+ iface, TYPE_MOBILE, teleManager.getSubscriberId());
+ reportActiveInterface(info);
+ }
+ } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
+ final LinkProperties prop = intent.getParcelableExtra(
+ WifiManager.EXTRA_LINK_PROPERTIES);
+ final String iface = prop != null ? prop.getInterfaceName() : null;
+ if (iface != null) {
+ final InterfaceInfo info = new InterfaceInfo(iface, TYPE_WIFI, null);
+ reportActiveInterface(info);
+ }
+ }
+ }
+ };
+
+ private BroadcastReceiver mPollReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // already running on background handler, network/io is safe, and
+ // caller verified to have UPDATE_DEVICE_STATS permission above.
+ synchronized (mStatsLock) {
+ // TODO: acquire wakelock while performing poll
+ performPollLocked();
+ }
+ }
+ };
+
+ private BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // persist stats during clean shutdown
+ synchronized (mStatsLock) {
+ writeStatsLocked();
+ }
+ }
+ };
+
+ private void performPollLocked() {
+ if (LOGD) Slog.v(TAG, "performPollLocked()");
+
+ // try refreshing time source when stale
+ if (mTime.getCacheAge() > TIME_CACHE_MAX_AGE) {
+ mTime.forceRefresh();
+ }
+
+ // TODO: consider marking "untrusted" times in historical stats
+ final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis()
+ : System.currentTimeMillis();
+
+ final NetworkStats current;
+ try {
+ current = mNetworkManager.getNetworkStatsSummary();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "problem reading network stats");
+ return;
+ }
+
+ // update historical usage with delta since last poll
+ final NetworkStats pollDelta = computeStatsDelta(mLastPollStats, current);
+ final long timeStart = currentTime - pollDelta.elapsedRealtime;
+ for (String iface : pollDelta.getKnownIfaces()) {
+ final InterfaceInfo info = mActiveIface.get(iface);
+ if (info == null) {
+ if (LOGD) Slog.w(TAG, "unknown interface " + iface + ", ignoring stats");
+ continue;
+ }
+
+ final int index = pollDelta.findIndex(iface, UID_ALL);
+ final long rx = pollDelta.rx[index];
+ final long tx = pollDelta.tx[index];
+
+ final NetworkStatsHistory history = findOrCreateHistoryLocked(info);
+ history.recordData(timeStart, currentTime, rx, tx);
+ history.removeBucketsBefore(currentTime - SUMMARY_MAX_HISTORY);
+ }
+
+ mLastPollStats = current;
+
+ // decide if enough has changed to trigger persist
+ final NetworkStats persistDelta = computeStatsDelta(mLastPersistStats, current);
+ for (String iface : persistDelta.getKnownIfaces()) {
+ final int index = persistDelta.findIndex(iface, UID_ALL);
+ if (persistDelta.rx[index] > SUMMARY_PERSIST_THRESHOLD
+ || persistDelta.tx[index] > SUMMARY_PERSIST_THRESHOLD) {
+ writeStatsLocked();
+ mLastPersistStats = current;
+ break;
+ }
+ }
+ }
+
+ private NetworkStatsHistory findOrCreateHistoryLocked(InterfaceInfo info) {
+ NetworkStatsHistory stats = mIfaceStats.get(info);
+ if (stats == null) {
+ stats = new NetworkStatsHistory(
+ info.networkType, info.identity, UID_ALL, SUMMARY_BUCKET_DURATION);
+ mIfaceStats.put(info, stats);
+ }
+ return stats;
+ }
+
+ private void readStatsLocked() {
+ if (LOGD) Slog.v(TAG, "readStatsLocked()");
+ // TODO: read historical stats from disk using AtomicFile
+ }
+
+ private void writeStatsLocked() {
+ if (LOGD) Slog.v(TAG, "writeStatsLocked()");
+ // TODO: persist historical stats to disk using AtomicFile
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ mContext.enforceCallingOrSelfPermission(DUMP, TAG);
+
+ pw.println("Active interfaces:");
+ for (InterfaceInfo info : mActiveIface.values()) {
+ info.dump(" ", pw);
+ }
+
+ pw.println("Known historical stats:");
+ for (NetworkStatsHistory stats : mIfaceStats.values()) {
+ stats.dump(" ", pw);
+ }
+ }
+
+ /**
+ * Details for a well-known network interface, including its name, network
+ * type, and billing relationship identity (such as IMSI).
+ */
+ private static class InterfaceInfo {
+ public final String iface;
+ public final int networkType;
+ public final String identity;
+
+ public InterfaceInfo(String iface, int networkType, String identity) {
+ this.iface = iface;
+ this.networkType = networkType;
+ this.identity = identity;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((identity == null) ? 0 : identity.hashCode());
+ result = prime * result + ((iface == null) ? 0 : iface.hashCode());
+ result = prime * result + networkType;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof InterfaceInfo) {
+ final InterfaceInfo info = (InterfaceInfo) obj;
+ return equal(iface, info.iface) && networkType == info.networkType
+ && equal(identity, info.identity);
+ }
+ return false;
+ }
+
+ public void dump(String prefix, PrintWriter pw) {
+ pw.print(prefix);
+ pw.print("InterfaceInfo: iface="); pw.print(iface);
+ pw.print(" networkType="); pw.print(networkType);
+ pw.print(" identity="); pw.println(identity);
+ }
+ }
+
+ private void reportActiveInterface(InterfaceInfo info) {
+ synchronized (mStatsLock) {
+ // TODO: when interface redefined, port over historical stats
+ mActiveIface.put(info.iface, info);
+ }
+ }
+
+ /**
+ * Return the delta between two {@link NetworkStats} snapshots, where {@code
+ * before} can be {@code null}.
+ */
+ private static NetworkStats computeStatsDelta(NetworkStats before, NetworkStats current) {
+ if (before != null) {
+ return current.subtract(before, false);
+ } else {
+ return current;
+ }
+ }
+
+ private static boolean equal(Object a, Object b) {
+ return a == b || (a != null && a.equals(b));
+ }
+
+ private static <T> T checkNotNull(T value, String message) {
+ if (value == null) {
+ throw new NullPointerException(message);
+ }
+ return value;
+ }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
index cf1171f..6552cdf 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
@@ -32,6 +32,7 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.INetworkPolicyListener;
+import android.net.INetworkStatsService;
import android.os.Binder;
import android.os.IPowerManager;
import android.test.AndroidTestCase;
@@ -57,6 +58,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
private IActivityManager mActivityManager;
private IPowerManager mPowerManager;
+ private INetworkStatsService mStatsService;
private INetworkPolicyListener mPolicyListener;
private NetworkPolicyManagerService mService;
@@ -90,10 +92,11 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
mActivityManager = createMock(IActivityManager.class);
mPowerManager = createMock(IPowerManager.class);
+ mStatsService = createMock(INetworkStatsService.class);
mPolicyListener = createMock(INetworkPolicyListener.class);
mService = new NetworkPolicyManagerService(
- mServiceContext, mActivityManager, mPowerManager);
+ mServiceContext, mActivityManager, mPowerManager, mStatsService);
// RemoteCallbackList needs a binder to use as key
expect(mPolicyListener.asBinder()).andReturn(mStubBinder).atLeastOnce();
@@ -123,6 +126,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
mActivityManager = null;
mPowerManager = null;
+ mStatsService = null;
mPolicyListener = null;
mService = null;
@@ -262,11 +266,11 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
}
private void replay() {
- EasyMock.replay(mActivityManager, mPowerManager, mPolicyListener);
+ EasyMock.replay(mActivityManager, mPowerManager, mStatsService, mPolicyListener);
}
private void verifyAndReset() {
- EasyMock.verify(mActivityManager, mPowerManager, mPolicyListener);
- EasyMock.reset(mActivityManager, mPowerManager, mPolicyListener);
+ EasyMock.verify(mActivityManager, mPowerManager, mStatsService, mPolicyListener);
+ EasyMock.reset(mActivityManager, mPowerManager, mStatsService, mPolicyListener);
}
}