Map network identity using ConnectivityService.

Instead of deriving network identity based on raw subsystem broadcasts,
listen for updates from ConnectivityService.  Added atomic view of all
active NetworkState, and build map from "iface" to NetworkIdentity set
for stats tracking.

To avoid exposing internal complexity, INetworkStatsService calls use
general templates.  Added TelephonyManager mapping to classify network
types using broad labels like "3G" or "4G", used to drive templates.

Cleaned up Objects and Preconditions.

Change-Id: I1d4c1403f0503bc3635a59bb378841ba42239a91
This commit is contained in:
Jeff Sharkey
2011-05-28 20:56:34 -07:00
parent 9ab8c6a64d
commit 1a3eb889f0
5 changed files with 253 additions and 149 deletions

View File

@@ -16,12 +16,18 @@
package android.net;
import android.net.NetworkStats;
import android.net.NetworkStatsHistory;
/** {@hide} */
interface INetworkStatsService {
NetworkStatsHistory[] getNetworkStatsSummary(int networkType);
NetworkStatsHistory getNetworkStatsUid(int uid);
/** Return historical stats for traffic that matches template. */
NetworkStatsHistory getHistoryForNetwork(int networkTemplate);
/** Return historical stats for specific UID traffic that matches template. */
NetworkStatsHistory getHistoryForUid(int uid, int networkTemplate);
/** Return usage summary per UID for traffic that matches template. */
NetworkStats getSummaryPerUid(long start, long end, int networkTemplate);
}

View File

@@ -40,26 +40,17 @@ import java.util.Arrays;
public class NetworkStatsHistory implements Parcelable {
private static final int VERSION = 1;
/** {@link #uid} value when UID details unavailable. */
public static final int UID_ALL = -1;
// TODO: teach about zigzag encoding to use less disk space
// TODO: teach how to convert between bucket sizes
public final int networkType;
public final String identity;
public final int uid;
public final long bucketDuration;
int bucketCount;
long[] bucketStart;
long[] rx;
long[] tx;
public int bucketCount;
public long[] bucketStart;
public long[] rx;
public long[] tx;
public NetworkStatsHistory(int networkType, String identity, int uid, long bucketDuration) {
this.networkType = networkType;
this.identity = identity;
this.uid = uid;
public NetworkStatsHistory(long bucketDuration) {
this.bucketDuration = bucketDuration;
bucketStart = new long[0];
rx = new long[0];
@@ -68,9 +59,6 @@ public class NetworkStatsHistory implements Parcelable {
}
public NetworkStatsHistory(Parcel in) {
networkType = in.readInt();
identity = in.readString();
uid = in.readInt();
bucketDuration = in.readLong();
bucketStart = readLongArray(in);
rx = in.createLongArray();
@@ -80,9 +68,6 @@ public class NetworkStatsHistory implements Parcelable {
/** {@inheritDoc} */
public void writeToParcel(Parcel out, int flags) {
out.writeInt(networkType);
out.writeString(identity);
out.writeInt(uid);
out.writeLong(bucketDuration);
writeLongArray(out, bucketStart, bucketCount);
writeLongArray(out, rx, bucketCount);
@@ -91,9 +76,6 @@ public class NetworkStatsHistory implements Parcelable {
public NetworkStatsHistory(DataInputStream in) throws IOException {
final int version = in.readInt();
networkType = in.readInt();
identity = in.readUTF();
uid = in.readInt();
bucketDuration = in.readLong();
bucketStart = readLongArray(in);
rx = readLongArray(in);
@@ -103,9 +85,6 @@ public class NetworkStatsHistory implements Parcelable {
public void writeToStream(DataOutputStream out) throws IOException {
out.writeInt(VERSION);
out.writeInt(networkType);
out.writeUTF(identity);
out.writeInt(uid);
out.writeLong(bucketDuration);
writeLongArray(out, bucketStart, bucketCount);
writeLongArray(out, rx, bucketCount);
@@ -214,11 +193,8 @@ public class NetworkStatsHistory implements Parcelable {
}
public void dump(String prefix, PrintWriter pw) {
// TODO: consider stripping identity when dumping
pw.print(prefix);
pw.print("NetworkStatsHistory: networkType="); pw.print(networkType);
pw.print(" identity="); pw.print(identity);
pw.print(" uid="); pw.println(uid);
pw.println("NetworkStatsHistory:");
for (int i = 0; i < bucketCount; i++) {
pw.print(prefix);
pw.print(" timestamp="); pw.print(bucketStart[i]);

View File

@@ -41,6 +41,42 @@ public class TrafficStats {
*/
public final static int UNSUPPORTED = -1;
// TODO: find better home for these template constants
/**
* Template to combine all {@link ConnectivityManager#TYPE_MOBILE} style
* networks together. Only uses statistics for currently active IMSI.
*
* @hide
*/
public static final int TEMPLATE_MOBILE_ALL = 1;
/**
* Template to combine all {@link ConnectivityManager#TYPE_MOBILE} style
* networks together that roughly meet a "3G" definition, or lower. Only
* uses statistics for currently active IMSI.
*
* @hide
*/
public static final int TEMPLATE_MOBILE_3G_LOWER = 2;
/**
* Template to combine all {@link ConnectivityManager#TYPE_MOBILE} style
* networks together that meet a "4G" definition. Only uses statistics for
* currently active IMSI.
*
* @hide
*/
public static final int TEMPLATE_MOBILE_4G = 3;
/**
* Template to combine all {@link ConnectivityManager#TYPE_WIFI} style
* networks together.
*
* @hide
*/
public static final int TEMPLATE_WIFI = 4;
/**
* Snapshot of {@link NetworkStats} when the currently active profiling
* session started, or {@code null} if no session active.

View File

@@ -0,0 +1,73 @@
/*
* 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 com.android.server.net;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ProtocolException;
import java.util.HashSet;
/**
* Identity of a {@code iface}, defined by the set of {@link NetworkIdentity}
* active on that interface.
*
* @hide
*/
public class InterfaceIdentity extends HashSet<NetworkIdentity> {
private static final int VERSION_CURRENT = 1;
public InterfaceIdentity() {
}
public InterfaceIdentity(DataInputStream in) throws IOException {
final int version = in.readInt();
switch (version) {
case VERSION_CURRENT: {
final int size = in.readInt();
for (int i = 0; i < size; i++) {
add(new NetworkIdentity(in));
}
break;
}
default: {
throw new ProtocolException("unexpected version: " + version);
}
}
}
public void writeToStream(DataOutputStream out) throws IOException {
out.writeInt(VERSION_CURRENT);
out.writeInt(size());
for (NetworkIdentity ident : this) {
ident.writeToStream(out);
}
}
/**
* Test if any {@link NetworkIdentity} on this interface matches the given
* template and IMEI.
*/
public boolean matchesTemplate(int networkTemplate, String subscriberId) {
for (NetworkIdentity ident : this) {
if (ident.matchesTemplate(networkTemplate, subscriberId)) {
return true;
}
}
return false;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2008 The Android Open Source Project
* 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.
@@ -16,12 +16,13 @@
package com.android.server.net;
import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
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.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.NetworkStats.UID_ALL;
import static com.android.internal.util.Preconditions.checkNotNull;
import android.app.AlarmManager;
import android.app.IAlarmManager;
@@ -30,11 +31,12 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.IConnectivityManager;
import android.net.INetworkStatsService;
import android.net.LinkProperties;
import android.net.NetworkInfo;
import android.net.NetworkState;
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;
@@ -46,12 +48,12 @@ 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.Lists;
import com.google.android.collect.Maps;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
/**
@@ -67,6 +69,8 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
private final IAlarmManager mAlarmManager;
private final TrustedTime mTime;
private IConnectivityManager mConnManager;
private static final String ACTION_NETWORK_STATS_POLL =
"com.android.server.action.NETWORK_STATS_POLL";
@@ -94,9 +98,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
private final Object mStatsLock = new Object();
/** Set of active ifaces during this boot. */
private HashMap<String, InterfaceInfo> mActiveIface = Maps.newHashMap();
private HashMap<String, InterfaceIdentity> mActiveIface = Maps.newHashMap();
/** Set of historical stats for known ifaces. */
private HashMap<InterfaceInfo, NetworkStatsHistory> mIfaceStats = Maps.newHashMap();
private HashMap<InterfaceIdentity, NetworkStatsHistory> mStats = Maps.newHashMap();
private NetworkStats mLastPollStats;
private NetworkStats mLastPersistStats;
@@ -132,13 +136,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
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);
final IntentFilter ifaceFilter = new IntentFilter();
ifaceFilter.addAction(CONNECTIVITY_ACTION);
mContext.registerReceiver(mIfaceReceiver, ifaceFilter, CONNECTIVITY_INTERNAL, mHandler);
// listen for periodic polling events
final IntentFilter pollFilter = new IntentFilter(ACTION_NETWORK_STATS_POLL);
@@ -155,6 +155,10 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
}
}
public void bindConnectivityManager(IConnectivityManager connManager) {
mConnManager = checkNotNull(connManager, "missing IConnectivityManager");
}
/**
* Clear any existing {@link #ACTION_NETWORK_STATS_POLL} alarms, and
* reschedule based on current {@link #POLL_INTERVAL} value.
@@ -173,45 +177,54 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
}
@Override
public NetworkStatsHistory[] getNetworkStatsSummary(int networkType) {
// TODO: return history for requested types
return null;
public NetworkStatsHistory getHistoryForNetwork(int networkTemplate) {
// TODO: create relaxed permission for reading stats
mContext.enforceCallingOrSelfPermission(UPDATE_DEVICE_STATS, TAG);
synchronized (mStatsLock) {
// combine all interfaces that match template
final String subscriberId = getActiveSubscriberId();
final NetworkStatsHistory combined = new NetworkStatsHistory(SUMMARY_BUCKET_DURATION);
for (InterfaceIdentity ident : mStats.keySet()) {
final NetworkStatsHistory history = mStats.get(ident);
if (ident.matchesTemplate(networkTemplate, subscriberId)) {
// TODO: combine all matching history data into a single history
}
}
return combined;
}
}
@Override
public NetworkStatsHistory getNetworkStatsUid(int uid) {
public NetworkStatsHistory getHistoryForUid(int uid, int networkTemplate) {
// TODO: create relaxed permission for reading stats
mContext.enforceCallingOrSelfPermission(UPDATE_DEVICE_STATS, TAG);
// TODO: return history for requested uid
return null;
}
@Override
public NetworkStats getSummaryPerUid(long start, long end, int networkTemplate) {
// TODO: create relaxed permission for reading stats
mContext.enforceCallingOrSelfPermission(UPDATE_DEVICE_STATS, TAG);
// TODO: total UID-granularity usage between time range
return null;
}
/**
* Receiver that watches for other system components that claim network
* Receiver that watches for {@link IConnectivityManager} to claim network
* interfaces. Used to associate {@link TelephonyManager#getSubscriberId()}
* with mobile interfaces.
*/
private BroadcastReceiver mInterfaceReceiver = new BroadcastReceiver() {
private BroadcastReceiver mIfaceReceiver = 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);
}
// on background handler thread, and verified CONNECTIVITY_INTERNAL
// permission above.
synchronized (mStatsLock) {
updateIfacesLocked();
}
}
};
@@ -219,8 +232,8 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
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.
// on background handler thread, and verified UPDATE_DEVICE_STATS
// permission above.
synchronized (mStatsLock) {
// TODO: acquire wakelock while performing poll
performPollLocked();
@@ -231,13 +244,49 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
private BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// persist stats during clean shutdown
// verified SHUTDOWN permission above.
synchronized (mStatsLock) {
writeStatsLocked();
}
}
};
/**
* Inspect all current {@link NetworkState} to derive mapping from {@code
* iface} to {@link NetworkStatsHistory}. When multiple {@link NetworkInfo}
* are active on a single {@code iface}, they are combined under a single
* {@link InterfaceIdentity}.
*/
private void updateIfacesLocked() {
if (LOGD) Slog.v(TAG, "updateIfacesLocked()");
// take one last stats snapshot before updating iface mapping. this
// isn't perfect, since the kernel may already be counting traffic from
// the updated network.
// TODO: verify that we only poll summary stats, not uid details
performPollLocked();
final NetworkState[] states;
try {
states = mConnManager.getAllNetworkState();
} catch (RemoteException e) {
Slog.w(TAG, "problem reading network state");
return;
}
// rebuild active interfaces based on connected networks
mActiveIface.clear();
for (NetworkState state : states) {
if (state.networkInfo.isConnected()) {
// collect networks under their parent interfaces
final String iface = state.linkProperties.getInterfaceName();
final InterfaceIdentity ident = findOrCreateInterfaceLocked(iface);
ident.add(NetworkIdentity.buildNetworkIdentity(mContext, state));
}
}
}
private void performPollLocked() {
if (LOGD) Slog.v(TAG, "performPollLocked()");
@@ -258,13 +307,15 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
return;
}
final ArrayList<String> unknownIface = Lists.newArrayList();
// 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");
final InterfaceIdentity ident = mActiveIface.get(iface);
if (ident == null) {
unknownIface.add(iface);
continue;
}
@@ -272,11 +323,15 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
final long rx = pollDelta.rx[index];
final long tx = pollDelta.tx[index];
final NetworkStatsHistory history = findOrCreateHistoryLocked(info);
final NetworkStatsHistory history = findOrCreateHistoryLocked(ident);
history.recordData(timeStart, currentTime, rx, tx);
history.removeBucketsBefore(currentTime - SUMMARY_MAX_HISTORY);
}
if (LOGD && unknownIface.size() > 0) {
Slog.w(TAG, "unknown interfaces " + unknownIface.toString() + ", ignoring those stats");
}
mLastPollStats = current;
// decide if enough has changed to trigger persist
@@ -292,16 +347,24 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
}
}
private NetworkStatsHistory findOrCreateHistoryLocked(InterfaceInfo info) {
NetworkStatsHistory stats = mIfaceStats.get(info);
private NetworkStatsHistory findOrCreateHistoryLocked(InterfaceIdentity ident) {
NetworkStatsHistory stats = mStats.get(ident);
if (stats == null) {
stats = new NetworkStatsHistory(
info.networkType, info.identity, UID_ALL, SUMMARY_BUCKET_DURATION);
mIfaceStats.put(info, stats);
stats = new NetworkStatsHistory(SUMMARY_BUCKET_DURATION);
mStats.put(ident, stats);
}
return stats;
}
private InterfaceIdentity findOrCreateInterfaceLocked(String iface) {
InterfaceIdentity ident = mActiveIface.get(iface);
if (ident == null) {
ident = new InterfaceIdentity();
mActiveIface.put(iface, ident);
}
return ident;
}
private void readStatsLocked() {
if (LOGD) Slog.v(TAG, "readStatsLocked()");
// TODO: read historical stats from disk using AtomicFile
@@ -310,6 +373,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
private void writeStatsLocked() {
if (LOGD) Slog.v(TAG, "writeStatsLocked()");
// TODO: persist historical stats to disk using AtomicFile
// TODO: consider duplicating stats and releasing lock while writing
}
@Override
@@ -317,63 +381,17 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
mContext.enforceCallingOrSelfPermission(DUMP, TAG);
pw.println("Active interfaces:");
for (InterfaceInfo info : mActiveIface.values()) {
info.dump(" ", pw);
for (String iface : mActiveIface.keySet()) {
final InterfaceIdentity ident = mActiveIface.get(iface);
pw.print(" iface="); pw.print(iface);
pw.print(" ident="); pw.println(ident.toString());
}
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);
for (InterfaceIdentity ident : mStats.keySet()) {
final NetworkStatsHistory stats = mStats.get(ident);
pw.print(" ident="); pw.println(ident.toString());
stats.dump(" ", pw);
}
}
@@ -389,15 +407,10 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
}
}
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;
private String getActiveSubscriberId() {
final TelephonyManager telephony = (TelephonyManager) mContext.getSystemService(
Context.TELEPHONY_SERVICE);
return telephony.getSubscriberId();
}
}