From 583d9568861f9f8a16824feea7ec768a147567b7 Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Tue, 18 Nov 2014 18:22:21 -0800 Subject: [PATCH] Lightweight checkin output for network stats. Define and print a compact version of network statistics when dump is requested with the "--checkin" flag. Defaults to last 24 hours, but included data can be tweaked with various flags. Groups together detailed network identities into larger umbrella terms like "mobile" and "wifi." Bug: 18415963 Change-Id: I70cf9c828ea5c6e5bb6884837d3608f66fbad2e6 --- core/java/android/net/NetworkIdentity.java | 24 ++++- core/java/android/net/NetworkStats.java | 16 +++ .../java/android/net/NetworkStatsHistory.java | 48 +++++++-- core/java/android/net/NetworkTemplate.java | 28 +++++- .../server/net/NetworkIdentitySet.java | 13 ++- .../server/net/NetworkStatsCollection.java | 99 ++++++++++++++++--- .../server/net/NetworkStatsRecorder.java | 51 +++++++--- .../server/net/NetworkStatsService.java | 36 +++++-- 8 files changed, 261 insertions(+), 54 deletions(-) diff --git a/core/java/android/net/NetworkIdentity.java b/core/java/android/net/NetworkIdentity.java index 36dd2fdfbc..d36707e4cb 100644 --- a/core/java/android/net/NetworkIdentity.java +++ b/core/java/android/net/NetworkIdentity.java @@ -34,7 +34,7 @@ import java.util.Objects; * * @hide */ -public class NetworkIdentity { +public class NetworkIdentity implements Comparable { /** * When enabled, combine all {@link #mSubType} together under * {@link #SUBTYPE_COMBINED}. @@ -76,7 +76,7 @@ public class NetworkIdentity { @Override public String toString() { - final StringBuilder builder = new StringBuilder("["); + final StringBuilder builder = new StringBuilder("{"); builder.append("type=").append(getNetworkTypeName(mType)); builder.append(", subType="); if (COMBINE_SUBTYPE_ENABLED) { @@ -95,7 +95,7 @@ public class NetworkIdentity { if (mRoaming) { builder.append(", ROAMING"); } - return builder.append("]").toString(); + return builder.append("}").toString(); } public int getType() { @@ -170,4 +170,22 @@ public class NetworkIdentity { return new NetworkIdentity(type, subType, subscriberId, networkId, roaming); } + + @Override + public int compareTo(NetworkIdentity another) { + int res = Integer.compare(mType, another.mType); + if (res == 0) { + res = Integer.compare(mSubType, another.mSubType); + } + if (res == 0 && mSubscriberId != null && another.mSubscriberId != null) { + res = mSubscriberId.compareTo(another.mSubscriberId); + } + if (res == 0 && mNetworkId != null && another.mNetworkId != null) { + res = mNetworkId.compareTo(another.mNetworkId); + } + if (res == 0) { + res = Boolean.compare(mRoaming, another.mRoaming); + } + return res; + } } diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java index ea5dfd184f..2afe578b6b 100644 --- a/core/java/android/net/NetworkStats.java +++ b/core/java/android/net/NetworkStats.java @@ -732,6 +732,22 @@ public class NetworkStats implements Parcelable { } } + /** + * Return text description of {@link #set} value. + */ + public static String setToCheckinString(int set) { + switch (set) { + case SET_ALL: + return "all"; + case SET_DEFAULT: + return "def"; + case SET_FOREGROUND: + return "fg"; + default: + return "unk"; + } + } + /** * Return text description of {@link #tag} value. */ diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java index 62d8738ba6..4a4accbba5 100644 --- a/core/java/android/net/NetworkStatsHistory.java +++ b/core/java/android/net/NetworkStatsHistory.java @@ -26,6 +26,7 @@ import static android.net.NetworkStatsHistory.DataStreamUtils.writeVarLongArray; import static android.net.NetworkStatsHistory.Entry.UNKNOWN; import static android.net.NetworkStatsHistory.ParcelUtils.readLongArray; import static android.net.NetworkStatsHistory.ParcelUtils.writeLongArray; +import static android.text.format.DateUtils.SECOND_IN_MILLIS; import static com.android.internal.util.ArrayUtils.total; import android.os.Parcel; @@ -38,6 +39,7 @@ import java.io.CharArrayWriter; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; +import java.io.PrintWriter; import java.net.ProtocolException; import java.util.Arrays; import java.util.Random; @@ -573,8 +575,22 @@ public class NetworkStatsHistory implements Parcelable { return (long) (start + (r.nextFloat() * (end - start))); } + /** + * Quickly determine if this history intersects with given window. + */ + public boolean intersects(long start, long end) { + final long dataStart = getStart(); + final long dataEnd = getEnd(); + if (start >= dataStart && start <= dataEnd) return true; + if (end >= dataStart && end <= dataEnd) return true; + if (dataStart >= start && dataStart <= end) return true; + if (dataEnd >= start && dataEnd <= end) return true; + return false; + } + public void dump(IndentingPrintWriter pw, boolean fullHistory) { - pw.print("NetworkStatsHistory: bucketDuration="); pw.println(bucketDuration); + pw.print("NetworkStatsHistory: bucketDuration="); + pw.println(bucketDuration / SECOND_IN_MILLIS); pw.increaseIndent(); final int start = fullHistory ? 0 : Math.max(0, bucketCount - 32); @@ -583,19 +599,35 @@ public class NetworkStatsHistory implements Parcelable { } for (int i = start; i < bucketCount; i++) { - pw.print("bucketStart="); pw.print(bucketStart[i]); - if (activeTime != null) { pw.print(" activeTime="); pw.print(activeTime[i]); } - if (rxBytes != null) { pw.print(" rxBytes="); pw.print(rxBytes[i]); } - if (rxPackets != null) { pw.print(" rxPackets="); pw.print(rxPackets[i]); } - if (txBytes != null) { pw.print(" txBytes="); pw.print(txBytes[i]); } - if (txPackets != null) { pw.print(" txPackets="); pw.print(txPackets[i]); } - if (operations != null) { pw.print(" operations="); pw.print(operations[i]); } + pw.print("st="); pw.print(bucketStart[i] / SECOND_IN_MILLIS); + if (rxBytes != null) { pw.print(" rb="); pw.print(rxBytes[i]); } + if (rxPackets != null) { pw.print(" rp="); pw.print(rxPackets[i]); } + if (txBytes != null) { pw.print(" tb="); pw.print(txBytes[i]); } + if (txPackets != null) { pw.print(" tp="); pw.print(txPackets[i]); } + if (operations != null) { pw.print(" op="); pw.print(operations[i]); } pw.println(); } pw.decreaseIndent(); } + public void dumpCheckin(PrintWriter pw) { + pw.print("d,"); + pw.print(bucketDuration / SECOND_IN_MILLIS); + pw.println(); + + for (int i = 0; i < bucketCount; i++) { + pw.print("b,"); + pw.print(bucketStart[i] / SECOND_IN_MILLIS); pw.print(','); + if (rxBytes != null) { pw.print(rxBytes[i]); } else { pw.print("*"); } pw.print(','); + if (rxPackets != null) { pw.print(rxPackets[i]); } else { pw.print("*"); } pw.print(','); + if (txBytes != null) { pw.print(txBytes[i]); } else { pw.print("*"); } pw.print(','); + if (txPackets != null) { pw.print(txPackets[i]); } else { pw.print("*"); } pw.print(','); + if (operations != null) { pw.print(operations[i]); } else { pw.print("*"); } + pw.println(); + } + } + @Override public String toString() { final CharArrayWriter writer = new CharArrayWriter(); diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java index 27197cc8a3..b839e0ac69 100644 --- a/core/java/android/net/NetworkTemplate.java +++ b/core/java/android/net/NetworkTemplate.java @@ -16,6 +16,7 @@ package android.net; +import static android.net.ConnectivityManager.TYPE_BLUETOOTH; import static android.net.ConnectivityManager.TYPE_ETHERNET; import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.ConnectivityManager.TYPE_WIFI_P2P; @@ -34,10 +35,10 @@ import android.content.res.Resources; import android.os.Parcel; import android.os.Parcelable; -import java.util.Objects; - import com.android.internal.annotations.VisibleForTesting; +import java.util.Objects; + /** * Template definition used to generically match {@link NetworkIdentity}, * usually when collecting statistics. @@ -53,6 +54,7 @@ public class NetworkTemplate implements Parcelable { public static final int MATCH_ETHERNET = 5; public static final int MATCH_MOBILE_WILDCARD = 6; public static final int MATCH_WIFI_WILDCARD = 7; + public static final int MATCH_BLUETOOTH = 8; /** * Set of {@link NetworkInfo#getType()} that reflect data usage. @@ -134,6 +136,14 @@ public class NetworkTemplate implements Parcelable { return new NetworkTemplate(MATCH_ETHERNET, null, null); } + /** + * Template to combine all {@link ConnectivityManager#TYPE_BLUETOOTH} style + * networks together. + */ + public static NetworkTemplate buildTemplateBluetooth() { + return new NetworkTemplate(MATCH_BLUETOOTH, null, null); + } + private final int mMatchRule; private final String mSubscriberId; private final String mNetworkId; @@ -222,6 +232,8 @@ public class NetworkTemplate implements Parcelable { return matchesMobileWildcard(ident); case MATCH_WIFI_WILDCARD: return matchesWifiWildcard(ident); + case MATCH_BLUETOOTH: + return matchesBluetooth(ident); default: throw new IllegalArgumentException("unknown network template"); } @@ -316,6 +328,16 @@ public class NetworkTemplate implements Parcelable { } } + /** + * Check if matches Bluetooth network template. + */ + private boolean matchesBluetooth(NetworkIdentity ident) { + if (ident.mType == TYPE_BLUETOOTH) { + return true; + } + return false; + } + private static String getMatchRuleName(int matchRule) { switch (matchRule) { case MATCH_MOBILE_3G_LOWER: @@ -332,6 +354,8 @@ public class NetworkTemplate implements Parcelable { return "MOBILE_WILDCARD"; case MATCH_WIFI_WILDCARD: return "WIFI_WILDCARD"; + case MATCH_BLUETOOTH: + return "BLUETOOTH"; default: return "UNKNOWN"; } diff --git a/services/core/java/com/android/server/net/NetworkIdentitySet.java b/services/core/java/com/android/server/net/NetworkIdentitySet.java index 397f9f41d8..f230bb39be 100644 --- a/services/core/java/com/android/server/net/NetworkIdentitySet.java +++ b/services/core/java/com/android/server/net/NetworkIdentitySet.java @@ -29,7 +29,8 @@ import java.util.HashSet; * * @hide */ -public class NetworkIdentitySet extends HashSet { +public class NetworkIdentitySet extends HashSet implements + Comparable { private static final int VERSION_INIT = 1; private static final int VERSION_ADD_ROAMING = 2; private static final int VERSION_ADD_NETWORK_ID = 3; @@ -92,4 +93,14 @@ public class NetworkIdentitySet extends HashSet { return null; } } + + @Override + public int compareTo(NetworkIdentitySet another) { + if (isEmpty()) return -1; + if (another.isEmpty()) return 1; + + final NetworkIdentity ident = iterator().next(); + final NetworkIdentity anotherIdent = another.iterator().next(); + return ident.compareTo(anotherIdent); + } } diff --git a/services/core/java/com/android/server/net/NetworkStatsCollection.java b/services/core/java/com/android/server/net/NetworkStatsCollection.java index 475482f6dd..3ac20f72f9 100644 --- a/services/core/java/com/android/server/net/NetworkStatsCollection.java +++ b/services/core/java/com/android/server/net/NetworkStatsCollection.java @@ -22,15 +22,20 @@ 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.TrafficStats.UID_REMOVED; +import static android.text.format.DateUtils.SECOND_IN_MILLIS; +import static android.text.format.DateUtils.WEEK_IN_MILLIS; +import android.net.ConnectivityManager; import android.net.NetworkIdentity; import android.net.NetworkStats; import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; import android.net.TrafficStats; -import android.text.format.DateUtils; +import android.util.ArrayMap; import android.util.AtomicFile; +import libcore.io.IoUtils; + import com.android.internal.util.ArrayUtils; import com.android.internal.util.FileRotator; import com.android.internal.util.IndentingPrintWriter; @@ -44,15 +49,13 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.io.PrintWriter; import java.net.ProtocolException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.Map; import java.util.Objects; -import libcore.io.IoUtils; - /** * Collection of {@link NetworkStatsHistory}, stored based on combined key of * {@link NetworkIdentitySet}, UID, set, and tag. Knows how to persist itself. @@ -70,7 +73,7 @@ public class NetworkStatsCollection implements FileRotator.Reader { private static final int VERSION_UNIFIED_INIT = 16; - private HashMap mStats = Maps.newHashMap(); + private ArrayMap mStats = new ArrayMap<>(); private final long mBucketDuration; @@ -145,12 +148,13 @@ public class NetworkStatsCollection implements FileRotator.Reader { NetworkTemplate template, int uid, int set, int tag, int fields, long start, long end) { final NetworkStatsHistory combined = new NetworkStatsHistory( mBucketDuration, estimateBuckets(), fields); - for (Map.Entry entry : mStats.entrySet()) { - final Key key = entry.getKey(); + for (int i = 0; i < mStats.size(); i++) { + final Key key = mStats.keyAt(i); final boolean setMatches = set == SET_ALL || key.set == set; if (key.uid == uid && setMatches && key.tag == tag && templateMatches(template, key.ident)) { - combined.recordHistory(entry.getValue(), start, end); + final NetworkStatsHistory value = mStats.valueAt(i); + combined.recordHistory(value, start, end); } } return combined; @@ -170,11 +174,11 @@ public class NetworkStatsCollection implements FileRotator.Reader { // shortcut when we know stats will be empty if (start == end) return stats; - for (Map.Entry mapEntry : mStats.entrySet()) { - final Key key = mapEntry.getKey(); + for (int i = 0; i < mStats.size(); i++) { + final Key key = mStats.keyAt(i); if (templateMatches(template, key.ident)) { - final NetworkStatsHistory history = mapEntry.getValue(); - historyEntry = history.getValues(start, end, now, historyEntry); + final NetworkStatsHistory value = mStats.valueAt(i); + historyEntry = value.getValues(start, end, now, historyEntry); entry.iface = IFACE_ALL; entry.uid = key.uid; @@ -225,8 +229,10 @@ public class NetworkStatsCollection implements FileRotator.Reader { * into this collection. */ public void recordCollection(NetworkStatsCollection another) { - for (Map.Entry entry : another.mStats.entrySet()) { - recordHistory(entry.getKey(), entry.getValue()); + for (int i = 0; i < another.mStats.size(); i++) { + final Key key = another.mStats.keyAt(i); + final NetworkStatsHistory value = another.mStats.valueAt(i); + recordHistory(key, value); } } @@ -460,7 +466,7 @@ public class NetworkStatsCollection implements FileRotator.Reader { } private int estimateBuckets() { - return (int) (Math.min(mEndMillis - mStartMillis, DateUtils.WEEK_IN_MILLIS * 5) + return (int) (Math.min(mEndMillis - mStartMillis, WEEK_IN_MILLIS * 5) / mBucketDuration); } @@ -482,6 +488,54 @@ public class NetworkStatsCollection implements FileRotator.Reader { } } + public void dumpCheckin(PrintWriter pw, long start, long end) { + dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateMobileWildcard(), "cell"); + dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateWifiWildcard(), "wifi"); + dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateEthernet(), "eth"); + dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateBluetooth(), "bt"); + } + + /** + * Dump all contained stats that match requested parameters, but group + * together all matching {@link NetworkTemplate} under a single prefix. + */ + private void dumpCheckin(PrintWriter pw, long start, long end, NetworkTemplate groupTemplate, + String groupPrefix) { + final ArrayMap grouped = new ArrayMap<>(); + + // Walk through all history, grouping by matching network templates + for (int i = 0; i < mStats.size(); i++) { + final Key key = mStats.keyAt(i); + final NetworkStatsHistory value = mStats.valueAt(i); + + if (!templateMatches(groupTemplate, key.ident)) continue; + + final Key groupKey = new Key(null, key.uid, key.set, key.tag); + NetworkStatsHistory groupHistory = grouped.get(groupKey); + if (groupHistory == null) { + groupHistory = new NetworkStatsHistory(value.getBucketDuration()); + grouped.put(groupKey, groupHistory); + } + groupHistory.recordHistory(value, start, end); + } + + for (int i = 0; i < grouped.size(); i++) { + final Key key = grouped.keyAt(i); + final NetworkStatsHistory value = grouped.valueAt(i); + + if (value.size() == 0) continue; + + pw.print("c,"); + pw.print(groupPrefix); pw.print(','); + pw.print(key.uid); pw.print(','); + pw.print(NetworkStats.setToCheckinString(key.set)); pw.print(','); + pw.print(key.tag); + pw.println(); + + value.dumpCheckin(pw); + } + } + /** * Test if given {@link NetworkTemplate} matches any {@link NetworkIdentity} * in the given {@link NetworkIdentitySet}. @@ -528,7 +582,20 @@ public class NetworkStatsCollection implements FileRotator.Reader { @Override public int compareTo(Key another) { - return Integer.compare(uid, another.uid); + int res = 0; + if (ident != null && another.ident != null) { + res = ident.compareTo(another.ident); + } + if (res == 0) { + res = Integer.compare(uid, another.uid); + } + if (res == 0) { + res = Integer.compare(set, another.set); + } + if (res == 0) { + res = Integer.compare(tag, another.tag); + } + return res; } } } diff --git a/services/core/java/com/android/server/net/NetworkStatsRecorder.java b/services/core/java/com/android/server/net/NetworkStatsRecorder.java index cea084b518..d5d7667630 100644 --- a/services/core/java/com/android/server/net/NetworkStatsRecorder.java +++ b/services/core/java/com/android/server/net/NetworkStatsRecorder.java @@ -41,6 +41,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.Arrays; import java.util.HashSet; @@ -124,23 +125,36 @@ public class NetworkStatsRecorder { * as reference is valid. */ public NetworkStatsCollection getOrLoadCompleteLocked() { - NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null; - if (complete == null) { - if (LOGD) Slog.d(TAG, "getOrLoadCompleteLocked() reading from disk for " + mCookie); - try { - complete = new NetworkStatsCollection(mBucketDuration); - mRotator.readMatching(complete, Long.MIN_VALUE, Long.MAX_VALUE); - complete.recordCollection(mPending); - mComplete = new WeakReference(complete); - } catch (IOException e) { - Log.wtf(TAG, "problem completely reading network stats", e); - recoverFromWtf(); - } catch (OutOfMemoryError e) { - Log.wtf(TAG, "problem completely reading network stats", e); - recoverFromWtf(); - } + NetworkStatsCollection res = mComplete != null ? mComplete.get() : null; + if (res == null) { + res = loadLocked(Long.MIN_VALUE, Long.MAX_VALUE); + mComplete = new WeakReference(res); } - return complete; + return res; + } + + public NetworkStatsCollection getOrLoadPartialLocked(long start, long end) { + NetworkStatsCollection res = mComplete != null ? mComplete.get() : null; + if (res == null) { + res = loadLocked(start, end); + } + return res; + } + + private NetworkStatsCollection loadLocked(long start, long end) { + if (LOGD) Slog.d(TAG, "loadLocked() reading from disk for " + mCookie); + final NetworkStatsCollection res = new NetworkStatsCollection(mBucketDuration); + try { + mRotator.readMatching(res, start, end); + res.recordCollection(mPending); + } catch (IOException e) { + Log.wtf(TAG, "problem completely reading network stats", e); + recoverFromWtf(); + } catch (OutOfMemoryError e) { + Log.wtf(TAG, "problem completely reading network stats", e); + recoverFromWtf(); + } + return res; } /** @@ -384,6 +398,11 @@ public class NetworkStatsRecorder { } } + public void dumpCheckin(PrintWriter pw, long start, long end) { + // Only load and dump stats from the requested window + getOrLoadPartialLocked(start, end).dumpCheckin(pw, start, end); + } + /** * Recover from {@link FileRotator} failure by dumping state to * {@link DropBoxManager} and deleting contents. diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java index 150ad34669..61f9a267fe 100644 --- a/services/core/java/com/android/server/net/NetworkStatsService.java +++ b/services/core/java/com/android/server/net/NetworkStatsService.java @@ -104,6 +104,7 @@ import android.provider.Settings; import android.provider.Settings.Global; import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; +import android.text.format.DateUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.EventLog; @@ -1094,12 +1095,20 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } @Override - protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + protected void dump(FileDescriptor fd, PrintWriter rawWriter, String[] args) { mContext.enforceCallingOrSelfPermission(DUMP, TAG); + long duration = DateUtils.DAY_IN_MILLIS; final HashSet argSet = new HashSet(); for (String arg : args) { argSet.add(arg); + + if (arg.startsWith("--duration=")) { + try { + duration = Long.parseLong(arg.substring(11)); + } catch (NumberFormatException ignored) { + } + } } // usage: dumpsys netstats --full --uid --tag --poll --checkin @@ -1109,7 +1118,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { final boolean includeUid = argSet.contains("--uid") || argSet.contains("detail"); final boolean includeTag = argSet.contains("--tag") || argSet.contains("detail"); - final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); + final IndentingPrintWriter pw = new IndentingPrintWriter(rawWriter, " "); synchronized (mStatsLock) { if (poll) { @@ -1119,13 +1128,24 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } if (checkin) { - // list current stats files to verify rotation - pw.println("Current files:"); - pw.increaseIndent(); - for (String file : mBaseDir.list()) { - pw.println(file); + final long end = System.currentTimeMillis(); + final long start = end - duration; + + pw.print("v1,"); + pw.print(start / SECOND_IN_MILLIS); pw.print(','); + pw.print(end / SECOND_IN_MILLIS); pw.println(); + + pw.println("xt"); + mXtRecorder.dumpCheckin(rawWriter, start, end); + + if (includeUid) { + pw.println("uid"); + mUidRecorder.dumpCheckin(rawWriter, start, end); + } + if (includeTag) { + pw.println("tag"); + mUidTagRecorder.dumpCheckin(rawWriter, start, end); } - pw.decreaseIndent(); return; }