diff --git a/core/java/android/net/INetworkStatsService.aidl b/core/java/android/net/INetworkStatsService.aidl index b65506c411..0e883cf8ac 100644 --- a/core/java/android/net/INetworkStatsService.aidl +++ b/core/java/android/net/INetworkStatsService.aidl @@ -26,7 +26,7 @@ interface INetworkStatsService { /** Return historical network layer stats for traffic that matches template. */ NetworkStatsHistory getHistoryForNetwork(in NetworkTemplate template, int fields); /** Return historical network layer stats for specific UID traffic that matches template. */ - NetworkStatsHistory getHistoryForUid(in NetworkTemplate template, int uid, int tag, int fields); + NetworkStatsHistory getHistoryForUid(in NetworkTemplate template, int uid, int set, int tag, int fields); /** Return network layer usage summary for traffic that matches template. */ NetworkStats getSummaryForNetwork(in NetworkTemplate template, long start, long end); @@ -38,6 +38,8 @@ interface INetworkStatsService { /** Increment data layer count of operations performed for UID and tag. */ void incrementOperationCount(int uid, int tag, int operationCount); + /** Mark given UID as being in foreground for stats purposes. */ + void setUidForeground(int uid, boolean uidForeground); /** Force update of statistics. */ void forceUpdate(); diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java index f2fcb8fad8..272545d0dc 100644 --- a/core/java/android/net/NetworkStats.java +++ b/core/java/android/net/NetworkStats.java @@ -42,7 +42,13 @@ public class NetworkStats implements Parcelable { public static final String IFACE_ALL = null; /** {@link #uid} value when UID details unavailable. */ public static final int UID_ALL = -1; - /** {@link #tag} value for without tag. */ + /** {@link #set} value when all sets combined. */ + public static final int SET_ALL = -1; + /** {@link #set} value where background data is accounted. */ + public static final int SET_DEFAULT = 0; + /** {@link #set} value where foreground data is accounted. */ + public static final int SET_FOREGROUND = 1; + /** {@link #tag} value for total data across all tags. */ public static final int TAG_NONE = 0; /** @@ -53,6 +59,7 @@ public class NetworkStats implements Parcelable { private int size; private String[] iface; private int[] uid; + private int[] set; private int[] tag; private long[] rxBytes; private long[] rxPackets; @@ -63,6 +70,7 @@ public class NetworkStats implements Parcelable { public static class Entry { public String iface; public int uid; + public int set; public int tag; public long rxBytes; public long rxPackets; @@ -71,17 +79,19 @@ public class NetworkStats implements Parcelable { public long operations; public Entry() { - this(IFACE_ALL, UID_ALL, TAG_NONE, 0L, 0L, 0L, 0L, 0L); + this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L); } public Entry(long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) { - this(IFACE_ALL, UID_ALL, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets, operations); + this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets, + operations); } - public Entry(String iface, int uid, int tag, long rxBytes, long rxPackets, long txBytes, - long txPackets, long operations) { + public Entry(String iface, int uid, int set, int tag, long rxBytes, long rxPackets, + long txBytes, long txPackets, long operations) { this.iface = iface; this.uid = uid; + this.set = set; this.tag = tag; this.rxBytes = rxBytes; this.rxPackets = rxPackets; @@ -96,6 +106,7 @@ public class NetworkStats implements Parcelable { this.size = 0; this.iface = new String[initialSize]; this.uid = new int[initialSize]; + this.set = new int[initialSize]; this.tag = new int[initialSize]; this.rxBytes = new long[initialSize]; this.rxPackets = new long[initialSize]; @@ -109,6 +120,7 @@ public class NetworkStats implements Parcelable { size = parcel.readInt(); iface = parcel.createStringArray(); uid = parcel.createIntArray(); + set = parcel.createIntArray(); tag = parcel.createIntArray(); rxBytes = parcel.createLongArray(); rxPackets = parcel.createLongArray(); @@ -123,6 +135,7 @@ public class NetworkStats implements Parcelable { dest.writeInt(size); dest.writeStringArray(iface); dest.writeIntArray(uid); + dest.writeIntArray(set); dest.writeIntArray(tag); dest.writeLongArray(rxBytes); dest.writeLongArray(rxPackets); @@ -131,15 +144,18 @@ public class NetworkStats implements Parcelable { dest.writeLongArray(operations); } - public NetworkStats addValues(String iface, int uid, int tag, long rxBytes, long rxPackets, - long txBytes, long txPackets) { - return addValues(iface, uid, tag, rxBytes, rxPackets, txBytes, txPackets, 0L); + // @VisibleForTesting + public NetworkStats addIfaceValues( + String iface, long rxBytes, long rxPackets, long txBytes, long txPackets) { + return addValues( + iface, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets, 0L); } - public NetworkStats addValues(String iface, int uid, int tag, long rxBytes, long rxPackets, - long txBytes, long txPackets, long operations) { - return addValues( - new Entry(iface, uid, tag, rxBytes, rxPackets, txBytes, txPackets, operations)); + // @VisibleForTesting + public NetworkStats addValues(String iface, int uid, int set, int tag, long rxBytes, + long rxPackets, long txBytes, long txPackets, long operations) { + return addValues(new Entry( + iface, uid, set, tag, rxBytes, rxPackets, txBytes, txPackets, operations)); } /** @@ -151,6 +167,7 @@ public class NetworkStats implements Parcelable { final int newLength = Math.max(iface.length, 10) * 3 / 2; iface = Arrays.copyOf(iface, newLength); uid = Arrays.copyOf(uid, newLength); + set = Arrays.copyOf(set, newLength); tag = Arrays.copyOf(tag, newLength); rxBytes = Arrays.copyOf(rxBytes, newLength); rxPackets = Arrays.copyOf(rxPackets, newLength); @@ -161,6 +178,7 @@ public class NetworkStats implements Parcelable { iface[size] = entry.iface; uid[size] = entry.uid; + set[size] = entry.set; tag[size] = entry.tag; rxBytes[size] = entry.rxBytes; rxPackets[size] = entry.rxPackets; @@ -179,6 +197,7 @@ public class NetworkStats implements Parcelable { final Entry entry = recycle != null ? recycle : new Entry(); entry.iface = iface[i]; entry.uid = uid[i]; + entry.set = set[i]; entry.tag = tag[i]; entry.rxBytes = rxBytes[i]; entry.rxPackets = rxPackets[i]; @@ -201,19 +220,26 @@ public class NetworkStats implements Parcelable { return iface.length; } + @Deprecated public NetworkStats combineValues(String iface, int uid, int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) { return combineValues( - new Entry(iface, uid, tag, rxBytes, rxPackets, txBytes, txPackets, operations)); + iface, uid, SET_DEFAULT, tag, rxBytes, rxPackets, txBytes, txPackets, operations); + } + + public NetworkStats combineValues(String iface, int uid, int set, int tag, long rxBytes, + long rxPackets, long txBytes, long txPackets, long operations) { + return combineValues(new Entry( + iface, uid, set, tag, rxBytes, rxPackets, txBytes, txPackets, operations)); } /** * Combine given values with an existing row, or create a new row if - * {@link #findIndex(String, int, int)} is unable to find match. Can also be - * used to subtract values from existing rows. + * {@link #findIndex(String, int, int, int)} is unable to find match. Can + * also be used to subtract values from existing rows. */ public NetworkStats combineValues(Entry entry) { - final int i = findIndex(entry.iface, entry.uid, entry.tag); + final int i = findIndex(entry.iface, entry.uid, entry.set, entry.tag); if (i == -1) { // only create new entry when positive contribution addValues(entry); @@ -230,9 +256,10 @@ public class NetworkStats implements Parcelable { /** * Find first stats index that matches the requested parameters. */ - public int findIndex(String iface, int uid, int tag) { + public int findIndex(String iface, int uid, int set, int tag) { for (int i = 0; i < size; i++) { - if (Objects.equal(iface, this.iface[i]) && uid == this.uid[i] && tag == this.tag[i]) { + if (Objects.equal(iface, this.iface[i]) && uid == this.uid[i] && set == this.set[i] + && tag == this.tag[i]) { return i; } } @@ -246,7 +273,7 @@ public class NetworkStats implements Parcelable { */ public void spliceOperationsFrom(NetworkStats stats) { for (int i = 0; i < size; i++) { - final int j = stats.findIndex(IFACE_ALL, uid[i], tag[i]); + final int j = stats.findIndex(IFACE_ALL, uid[i], set[i], tag[i]); if (j == -1) { operations[i] = 0; } else { @@ -332,10 +359,11 @@ public class NetworkStats implements Parcelable { for (int i = 0; i < size; i++) { entry.iface = iface[i]; entry.uid = uid[i]; + entry.set = set[i]; entry.tag = tag[i]; // find remote row that matches, and subtract - final int j = value.findIndex(entry.iface, entry.uid, entry.tag); + final int j = value.findIndex(entry.iface, entry.uid, entry.set, entry.tag); if (j == -1) { // newly appearing row, return entire value entry.rxBytes = rxBytes[i]; @@ -377,7 +405,8 @@ public class NetworkStats implements Parcelable { pw.print(prefix); pw.print(" iface="); pw.print(iface[i]); pw.print(" uid="); pw.print(uid[i]); - pw.print(" tag=0x"); pw.print(Integer.toHexString(tag[i])); + pw.print(" set="); pw.print(setToString(set[i])); + pw.print(" tag="); pw.print(tagToString(tag[i])); pw.print(" rxBytes="); pw.print(rxBytes[i]); pw.print(" rxPackets="); pw.print(rxPackets[i]); pw.print(" txBytes="); pw.print(txBytes[i]); @@ -386,6 +415,29 @@ public class NetworkStats implements Parcelable { } } + /** + * Return text description of {@link #set} value. + */ + public static String setToString(int set) { + switch (set) { + case SET_ALL: + return "ALL"; + case SET_DEFAULT: + return "DEFAULT"; + case SET_FOREGROUND: + return "FOREGROUND"; + default: + return "UNKNOWN"; + } + } + + /** + * Return text description of {@link #tag} value. + */ + public static String tagToString(int tag) { + return "0x" + Integer.toHexString(tag); + } + @Override public String toString() { final CharArrayWriter writer = new CharArrayWriter(); diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java index 4ba44cac29..b4f15acff1 100644 --- a/core/java/android/net/NetworkStatsHistory.java +++ b/core/java/android/net/NetworkStatsHistory.java @@ -17,6 +17,7 @@ package android.net; import static android.net.NetworkStats.IFACE_ALL; +import static android.net.NetworkStats.SET_DEFAULT; import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkStats.UID_ALL; import static android.net.NetworkStatsHistory.DataStreamUtils.readFullLongArray; @@ -215,8 +216,8 @@ public class NetworkStatsHistory implements Parcelable { */ @Deprecated public void recordData(long start, long end, long rxBytes, long txBytes) { - recordData(start, end, - new NetworkStats.Entry(IFACE_ALL, UID_ALL, TAG_NONE, rxBytes, 0L, txBytes, 0L, 0L)); + recordData(start, end, new NetworkStats.Entry( + IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, 0L, txBytes, 0L, 0L)); } /** @@ -269,7 +270,7 @@ public class NetworkStatsHistory implements Parcelable { */ public void recordEntireHistory(NetworkStatsHistory input) { final NetworkStats.Entry entry = new NetworkStats.Entry( - IFACE_ALL, UID_ALL, TAG_NONE, 0L, 0L, 0L, 0L, 0L); + IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L); for (int i = 0; i < input.bucketCount; i++) { final long start = input.bucketStart[i]; final long end = start + input.bucketDuration; @@ -422,7 +423,7 @@ public class NetworkStatsHistory implements Parcelable { ensureBuckets(start, end); final NetworkStats.Entry entry = new NetworkStats.Entry( - IFACE_ALL, UID_ALL, TAG_NONE, 0L, 0L, 0L, 0L, 0L); + IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L); final Random r = new Random(); while (rxBytes > 1024 || rxPackets > 128 || txBytes > 1024 || txPackets > 128 || operations > 32) { diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java index f138e49140..c2c5c183d3 100644 --- a/core/java/android/net/TrafficStats.java +++ b/core/java/android/net/TrafficStats.java @@ -205,10 +205,6 @@ public class TrafficStats { * @param operationCount Number of operations to increment count by. */ public static void incrementOperationCount(int tag, int operationCount) { - if (operationCount < 0) { - throw new IllegalArgumentException("operation count can only be incremented"); - } - final INetworkStatsService statsService = INetworkStatsService.Stub.asInterface( ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); final int uid = android.os.Process.myUid(); diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java index deca7a91cf..c911687ab7 100644 --- a/services/java/com/android/server/net/NetworkStatsService.java +++ b/services/java/com/android/server/net/NetworkStatsService.java @@ -26,6 +26,9 @@ import static android.content.Intent.ACTION_UID_REMOVED; import static android.content.Intent.EXTRA_UID; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.NetworkStats.IFACE_ALL; +import static android.net.NetworkStats.SET_ALL; +import static android.net.NetworkStats.SET_DEFAULT; +import static android.net.NetworkStats.SET_FOREGROUND; import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkStats.UID_ALL; import static android.net.TrafficStats.UID_REMOVED; @@ -40,6 +43,8 @@ import static android.text.format.DateUtils.DAY_IN_MILLIS; import static android.text.format.DateUtils.HOUR_IN_MILLIS; import static android.text.format.DateUtils.MINUTE_IN_MILLIS; import static com.android.internal.util.Preconditions.checkNotNull; +import static com.android.server.NetworkManagementSocketTagger.resetKernelUidStats; +import static com.android.server.NetworkManagementSocketTagger.setKernelCounterSet; import android.app.AlarmManager; import android.app.IAlarmManager; @@ -63,17 +68,20 @@ import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; import android.os.INetworkManagementService; +import android.os.Message; import android.os.PowerManager; import android.os.RemoteException; import android.os.SystemClock; import android.provider.Settings; import android.telephony.TelephonyManager; -import android.util.LongSparseArray; import android.util.NtpTrustedTime; import android.util.Slog; +import android.util.SparseIntArray; import android.util.TrustedTime; import com.android.internal.os.AtomicFile; +import com.android.internal.util.Objects; +import com.google.android.collect.Lists; import com.google.android.collect.Maps; import com.google.android.collect.Sets; @@ -88,6 +96,8 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.net.ProtocolException; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -109,6 +119,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private static final int VERSION_UID_INIT = 1; private static final int VERSION_UID_WITH_IDENT = 2; private static final int VERSION_UID_WITH_TAG = 3; + private static final int VERSION_UID_WITH_SET = 4; + + private static final int MSG_FORCE_UPDATE = 0x1; private final Context mContext; private final INetworkManagementService mNetworkManager; @@ -156,8 +169,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { /** Set of historical network layer stats for known networks. */ private HashMap mNetworkStats = Maps.newHashMap(); /** Set of historical network layer stats for known UIDs. */ - private HashMap> mUidStats = - Maps.newHashMap(); + private HashMap mUidStats = Maps.newHashMap(); /** Flag if {@link #mUidStats} have been loaded from disk. */ private boolean mUidStatsLoaded = false; @@ -167,6 +179,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private NetworkStats mLastUidSnapshot; + /** Current counter sets for each UID. */ + private SparseIntArray mActiveUidCounterSet = new SparseIntArray(); + /** Data layer operation counters for splicing into other structures. */ private NetworkStats mOperations = new NetworkStats(0L, 10); private NetworkStats mLastOperationsSnapshot; @@ -177,11 +192,6 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private final AtomicFile mNetworkFile; private final AtomicFile mUidFile; - // 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) { this(context, networkManager, alarmManager, NtpTrustedTime.getInstance(context), @@ -207,7 +217,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { mHandlerThread = new HandlerThread(TAG); mHandlerThread.start(); - mHandler = new Handler(mHandlerThread.getLooper()); + mHandler = new Handler(mHandlerThread.getLooper(), mHandlerCallback); mNetworkFile = new AtomicFile(new File(systemDir, "netstats.bin")); mUidFile = new AtomicFile(new File(systemDir, "netstats_uid.bin")); @@ -246,6 +256,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } catch (RemoteException e) { Slog.w(TAG, "unable to register poll alarm"); } + + // kick off background poll to bootstrap deltas + mHandler.obtainMessage(MSG_FORCE_UPDATE).sendToTarget(); } private void shutdownLocked() { @@ -302,24 +315,24 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @Override public NetworkStatsHistory getHistoryForUid( - NetworkTemplate template, int uid, int tag, int fields) { + NetworkTemplate template, int uid, int set, int tag, int fields) { mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG); synchronized (mStatsLock) { ensureUidStatsLoadedLocked(); - final long packed = packUidAndTag(uid, tag); // combine all interfaces that match template final NetworkStatsHistory combined = new NetworkStatsHistory( mSettings.getUidBucketDuration(), estimateUidBuckets(), fields); - for (NetworkIdentitySet ident : mUidStats.keySet()) { - if (templateMatches(template, ident)) { - final NetworkStatsHistory history = mUidStats.get(ident).get(packed); - if (history != null) { - combined.recordEntireHistory(history); - } + for (UidStatsKey key : mUidStats.keySet()) { + final boolean setMatches = set == SET_ALL || key.set == set; + if (templateMatches(template, key.ident) && key.uid == uid && setMatches + && key.tag == tag) { + final NetworkStatsHistory history = mUidStats.get(key); + combined.recordEntireHistory(history); } } + return combined; } } @@ -371,33 +384,27 @@ public class NetworkStatsService extends INetworkStatsService.Stub { final NetworkStats.Entry entry = new NetworkStats.Entry(); NetworkStatsHistory.Entry historyEntry = null; - for (NetworkIdentitySet ident : mUidStats.keySet()) { - if (templateMatches(template, ident)) { - final LongSparseArray uidStats = mUidStats.get(ident); - for (int i = 0; i < uidStats.size(); i++) { - final long packed = uidStats.keyAt(i); - final int uid = unpackUid(packed); - final int tag = unpackTag(packed); + for (UidStatsKey key : mUidStats.keySet()) { + if (templateMatches(template, key.ident)) { + // always include summary under TAG_NONE, and include + // other tags when requested. + if (key.tag == TAG_NONE || includeTags) { + final NetworkStatsHistory history = mUidStats.get(key); + historyEntry = history.getValues(start, end, now, historyEntry); - // always include summary under TAG_NONE, and include - // other tags when requested. - if (tag == TAG_NONE || includeTags) { - final NetworkStatsHistory history = uidStats.valueAt(i); - historyEntry = history.getValues(start, end, now, historyEntry); + entry.iface = IFACE_ALL; + entry.uid = key.uid; + entry.set = key.set; + entry.tag = key.tag; + entry.rxBytes = historyEntry.rxBytes; + entry.rxPackets = historyEntry.rxPackets; + entry.txBytes = historyEntry.txBytes; + entry.txPackets = historyEntry.txPackets; + entry.operations = historyEntry.operations; - entry.iface = IFACE_ALL; - entry.uid = uid; - entry.tag = tag; - entry.rxBytes = historyEntry.rxBytes; - entry.rxPackets = historyEntry.rxPackets; - entry.txBytes = historyEntry.txBytes; - entry.txPackets = historyEntry.txPackets; - entry.operations = historyEntry.operations; - - if (entry.rxBytes > 0 || entry.rxPackets > 0 || entry.txBytes > 0 - || entry.txPackets > 0 || entry.operations > 0) { - stats.combineValues(entry); - } + if (entry.rxBytes > 0 || entry.rxPackets > 0 || entry.txBytes > 0 + || entry.txPackets > 0 || entry.operations > 0) { + stats.combineValues(entry); } } } @@ -437,8 +444,31 @@ public class NetworkStatsService extends INetworkStatsService.Stub { mContext.enforceCallingOrSelfPermission(MODIFY_NETWORK_ACCOUNTING, TAG); } + if (operationCount < 0) { + throw new IllegalArgumentException("operation count can only be incremented"); + } + if (tag == TAG_NONE) { + throw new IllegalArgumentException("operation count must have specific tag"); + } + synchronized (mStatsLock) { - mOperations.combineValues(IFACE_ALL, uid, tag, 0L, 0L, 0L, 0L, operationCount); + final int set = mActiveUidCounterSet.get(uid, SET_DEFAULT); + mOperations.combineValues(IFACE_ALL, uid, set, tag, 0L, 0L, 0L, 0L, operationCount); + mOperations.combineValues(IFACE_ALL, uid, set, TAG_NONE, 0L, 0L, 0L, 0L, operationCount); + } + } + + @Override + public void setUidForeground(int uid, boolean uidForeground) { + mContext.enforceCallingOrSelfPermission(MODIFY_NETWORK_ACCOUNTING, TAG); + + synchronized (mStatsLock) { + final int set = uidForeground ? SET_FOREGROUND : SET_DEFAULT; + final int oldSet = mActiveUidCounterSet.get(uid, SET_DEFAULT); + if (oldSet != set) { + mActiveUidCounterSet.put(uid, set); + setKernelCounterSet(uid, set); + } } } @@ -601,7 +631,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { NetworkStats.Entry entry = null; for (String iface : persistDelta.getUniqueIfaces()) { - final int index = persistDelta.findIndex(iface, UID_ALL, TAG_NONE); + final int index = persistDelta.findIndex(iface, UID_ALL, SET_DEFAULT, TAG_NONE); entry = persistDelta.getValues(index, entry); if (forcePersist || entry.rxBytes > persistThreshold || entry.txBytes > persistThreshold) { @@ -676,31 +706,28 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } // splice in operation counts since last poll - final int j = operationsDelta.findIndex(IFACE_ALL, entry.uid, entry.tag); + final int j = operationsDelta.findIndex(IFACE_ALL, entry.uid, entry.set, entry.tag); if (j != -1) { operationsEntry = operationsDelta.getValues(j, operationsEntry); entry.operations = operationsEntry.operations; } final NetworkStatsHistory history = findOrCreateUidStatsLocked( - ident, entry.uid, entry.tag); + ident, entry.uid, entry.set, entry.tag); history.recordData(timeStart, currentTime, entry); } // trim any history beyond max final long maxUidHistory = mSettings.getUidMaxHistory(); final long maxTagHistory = mSettings.getTagMaxHistory(); - for (LongSparseArray uidStats : mUidStats.values()) { - for (int i = 0; i < uidStats.size(); i++) { - final long packed = uidStats.keyAt(i); - final NetworkStatsHistory history = uidStats.valueAt(i); + for (UidStatsKey key : mUidStats.keySet()) { + final NetworkStatsHistory history = mUidStats.get(key); - // detailed tags are trimmed sooner than summary in TAG_NONE - if (unpackTag(packed) == TAG_NONE) { - history.removeBucketsBefore(currentTime - maxUidHistory); - } else { - history.removeBucketsBefore(currentTime - maxTagHistory); - } + // detailed tags are trimmed sooner than summary in TAG_NONE + if (key.tag == TAG_NONE) { + history.removeBucketsBefore(currentTime - maxUidHistory); + } else { + history.removeBucketsBefore(currentTime - maxTagHistory); } } @@ -715,26 +742,25 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private void removeUidLocked(int uid) { ensureUidStatsLoadedLocked(); + final ArrayList knownKeys = Lists.newArrayList(); + knownKeys.addAll(mUidStats.keySet()); + // migrate all UID stats into special "removed" bucket - for (NetworkIdentitySet ident : mUidStats.keySet()) { - final LongSparseArray uidStats = mUidStats.get(ident); - for (int i = 0; i < uidStats.size(); i++) { - final long packed = uidStats.keyAt(i); - if (unpackUid(packed) == uid) { - // only migrate combined TAG_NONE history - if (unpackTag(packed) == TAG_NONE) { - final NetworkStatsHistory uidHistory = uidStats.valueAt(i); - final NetworkStatsHistory removedHistory = findOrCreateUidStatsLocked( - ident, UID_REMOVED, TAG_NONE); - removedHistory.recordEntireHistory(uidHistory); - } - uidStats.remove(packed); + for (UidStatsKey key : knownKeys) { + if (key.uid == uid) { + // only migrate combined TAG_NONE history + if (key.tag == TAG_NONE) { + final NetworkStatsHistory uidHistory = mUidStats.get(key); + final NetworkStatsHistory removedHistory = findOrCreateUidStatsLocked( + key.ident, UID_REMOVED, SET_DEFAULT, TAG_NONE); + removedHistory.recordEntireHistory(uidHistory); } + mUidStats.remove(key); } } - // TODO: push kernel event to wipe stats for UID, otherwise we risk - // picking them up again during next poll. + // clear kernel stats associated with UID + resetKernelUidStats(uid); // since this was radical rewrite, push to disk writeUidStatsLocked(); @@ -763,17 +789,11 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } private NetworkStatsHistory findOrCreateUidStatsLocked( - NetworkIdentitySet ident, int uid, int tag) { + NetworkIdentitySet ident, int uid, int set, int tag) { ensureUidStatsLoadedLocked(); - LongSparseArray uidStats = mUidStats.get(ident); - if (uidStats == null) { - uidStats = new LongSparseArray(); - mUidStats.put(ident, uidStats); - } - - final long packed = packUidAndTag(uid, tag); - final NetworkStatsHistory existing = uidStats.get(packed); + final UidStatsKey key = new UidStatsKey(ident, uid, set, tag); + final NetworkStatsHistory existing = mUidStats.get(key); // update when no existing, or when bucket duration changed final long bucketDuration = mSettings.getUidBucketDuration(); @@ -787,7 +807,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } if (updated != null) { - uidStats.put(packed, updated); + mUidStats.put(key, updated); return updated; } else { return existing; @@ -874,25 +894,24 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // for a short time. break; } - case VERSION_UID_WITH_TAG: { - // uid := size *(NetworkIdentitySet size *(UID tag NetworkStatsHistory)) - final int ifaceSize = in.readInt(); - for (int i = 0; i < ifaceSize; i++) { + case VERSION_UID_WITH_TAG: + case VERSION_UID_WITH_SET: { + // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory)) + final int identSize = in.readInt(); + for (int i = 0; i < identSize; i++) { final NetworkIdentitySet ident = new NetworkIdentitySet(in); - final int childSize = in.readInt(); - final LongSparseArray uidStats = new LongSparseArray< - NetworkStatsHistory>(childSize); - for (int j = 0; j < childSize; j++) { + final int size = in.readInt(); + for (int j = 0; j < size; j++) { final int uid = in.readInt(); + final int set = (version >= VERSION_UID_WITH_SET) ? in.readInt() + : SET_DEFAULT; final int tag = in.readInt(); - final long packed = packUidAndTag(uid, tag); + final UidStatsKey key = new UidStatsKey(ident, uid, set, tag); final NetworkStatsHistory history = new NetworkStatsHistory(in); - uidStats.put(packed, history); + mUidStats.put(key, history); } - - mUidStats.put(ident, uidStats); } break; } @@ -949,29 +968,36 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // TODO: consider duplicating stats and releasing lock while writing + // build UidStatsKey lists grouped by ident + final HashMap> keysByIdent = Maps.newHashMap(); + for (UidStatsKey key : mUidStats.keySet()) { + ArrayList keys = keysByIdent.get(key.ident); + if (keys == null) { + keys = Lists.newArrayList(); + keysByIdent.put(key.ident, keys); + } + keys.add(key); + } + FileOutputStream fos = null; try { fos = mUidFile.startWrite(); final DataOutputStream out = new DataOutputStream(new BufferedOutputStream(fos)); out.writeInt(FILE_MAGIC); - out.writeInt(VERSION_UID_WITH_TAG); + out.writeInt(VERSION_UID_WITH_SET); - final int size = mUidStats.size(); - out.writeInt(size); - for (NetworkIdentitySet ident : mUidStats.keySet()) { - final LongSparseArray uidStats = mUidStats.get(ident); + out.writeInt(keysByIdent.size()); + for (NetworkIdentitySet ident : keysByIdent.keySet()) { + final ArrayList keys = keysByIdent.get(ident); ident.writeToStream(out); - final int childSize = uidStats.size(); - out.writeInt(childSize); - for (int i = 0; i < childSize; i++) { - final long packed = uidStats.keyAt(i); - final int uid = unpackUid(packed); - final int tag = unpackTag(packed); - final NetworkStatsHistory history = uidStats.valueAt(i); - out.writeInt(uid); - out.writeInt(tag); + out.writeInt(keys.size()); + for (UidStatsKey key : keys) { + final NetworkStatsHistory history = mUidStats.get(key); + out.writeInt(key.uid); + out.writeInt(key.set); + out.writeInt(key.tag); history.writeToStream(out); } } @@ -1030,20 +1056,19 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // from disk if not already in memory. ensureUidStatsLoadedLocked(); - pw.println("Detailed UID stats:"); - for (NetworkIdentitySet ident : mUidStats.keySet()) { - pw.print(" ident="); pw.println(ident.toString()); + final ArrayList keys = Lists.newArrayList(); + keys.addAll(mUidStats.keySet()); + Collections.sort(keys); - final LongSparseArray uidStats = mUidStats.get(ident); - for (int i = 0; i < uidStats.size(); i++) { - final long packed = uidStats.keyAt(i); - final int uid = unpackUid(packed); - final int tag = unpackTag(packed); - final NetworkStatsHistory history = uidStats.valueAt(i); - pw.print(" UID="); pw.print(uid); - pw.print(" tag=0x"); pw.println(Integer.toHexString(tag)); - history.dump(" ", pw, fullHistory); - } + pw.println("Detailed UID stats:"); + for (UidStatsKey key : keys) { + pw.print(" ident="); pw.print(key.ident.toString()); + pw.print(" uid="); pw.print(key.uid); + pw.print(" set="); pw.print(NetworkStats.setToString(key.set)); + pw.print(" tag="); pw.println(NetworkStats.tagToString(key.tag)); + + final NetworkStatsHistory history = mUidStats.get(key); + history.dump(" ", pw, fullHistory); } } } @@ -1080,8 +1105,12 @@ public class NetworkStatsService extends INetworkStatsService.Stub { for (ApplicationInfo info : installedApps) { final int uid = info.uid; - findOrCreateUidStatsLocked(ident, uid, TAG_NONE).generateRandom(UID_START, UID_END, - UID_RX_BYTES, UID_RX_PACKETS, UID_TX_BYTES, UID_TX_PACKETS, UID_OPERATIONS); + findOrCreateUidStatsLocked(ident, uid, SET_DEFAULT, TAG_NONE).generateRandom( + UID_START, UID_END, UID_RX_BYTES, UID_RX_PACKETS, UID_TX_BYTES, + UID_TX_PACKETS, UID_OPERATIONS); + findOrCreateUidStatsLocked(ident, uid, SET_FOREGROUND, TAG_NONE).generateRandom( + UID_START, UID_END, UID_RX_BYTES, UID_RX_PACKETS, UID_TX_BYTES, + UID_TX_PACKETS, UID_OPERATIONS); } } } @@ -1116,23 +1145,6 @@ public class NetworkStatsService extends INetworkStatsService.Stub { return (int) (existing.size() * existing.getBucketDuration() / newBucketDuration); } - // @VisibleForTesting - public static long packUidAndTag(int uid, int tag) { - final long uidLong = uid; - final long tagLong = tag; - return (uidLong << 32) | (tagLong & 0xFFFFFFFFL); - } - - // @VisibleForTesting - public static int unpackUid(long packed) { - return (int) (packed >> 32); - } - - // @VisibleForTesting - public static int unpackTag(long packed) { - return (int) (packed & 0xFFFFFFFFL); - } - /** * Test if given {@link NetworkTemplate} matches any {@link NetworkIdentity} * in the given {@link NetworkIdentitySet}. @@ -1146,6 +1158,58 @@ public class NetworkStatsService extends INetworkStatsService.Stub { return false; } + private Handler.Callback mHandlerCallback = new Handler.Callback() { + /** {@inheritDoc} */ + public boolean handleMessage(Message msg) { + switch (msg.what) { + case MSG_FORCE_UPDATE: { + forceUpdate(); + return true; + } + default: { + return false; + } + } + } + }; + + /** + * Key uniquely identifying a {@link NetworkStatsHistory} for a UID. + */ + private static class UidStatsKey implements Comparable { + public final NetworkIdentitySet ident; + public final int uid; + public final int set; + public final int tag; + + public UidStatsKey(NetworkIdentitySet ident, int uid, int set, int tag) { + this.ident = ident; + this.uid = uid; + this.set = set; + this.tag = tag; + } + + @Override + public int hashCode() { + return Objects.hashCode(ident, uid, set, tag); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof UidStatsKey) { + final UidStatsKey key = (UidStatsKey) obj; + return Objects.equal(ident, key.ident) && uid == key.uid && set == key.set + && tag == key.tag; + } + return false; + } + + /** {@inheritDoc} */ + public int compareTo(UidStatsKey another) { + return Integer.compare(uid, another.uid); + } + } + /** * Default external settings that read from {@link Settings.Secure}. */