Merge "Transition from DEV network stats to XT." into jb-dev
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index 844d055..fb7a4f8 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -111,6 +111,14 @@
&& operations == 0;
}
+ public void add(Entry another) {
+ this.rxBytes += another.rxBytes;
+ this.rxPackets += another.rxPackets;
+ this.txBytes += another.txBytes;
+ this.txPackets += another.txPackets;
+ this.operations += another.operations;
+ }
+
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java
index 0003c6e..a37c26f9 100644
--- a/core/java/android/net/NetworkStatsHistory.java
+++ b/core/java/android/net/NetworkStatsHistory.java
@@ -342,11 +342,23 @@
* for combining together stats for external reporting.
*/
public void recordEntireHistory(NetworkStatsHistory input) {
+ recordHistory(input, Long.MIN_VALUE, Long.MAX_VALUE);
+ }
+
+ /**
+ * Record given {@link NetworkStatsHistory} into this history, copying only
+ * buckets that atomically occur in the inclusive time range. Doesn't
+ * interpolate across partial buckets.
+ */
+ public void recordHistory(NetworkStatsHistory input, long start, long end) {
final NetworkStats.Entry entry = new NetworkStats.Entry(
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;
+ final long bucketStart = input.bucketStart[i];
+ final long bucketEnd = bucketStart + input.bucketDuration;
+
+ // skip when bucket is outside requested range
+ if (bucketStart < start || bucketEnd > end) continue;
entry.rxBytes = getLong(input.rxBytes, i, 0L);
entry.rxPackets = getLong(input.rxPackets, i, 0L);
@@ -354,7 +366,7 @@
entry.txPackets = getLong(input.txPackets, i, 0L);
entry.operations = getLong(input.operations, i, 0L);
- recordData(start, end, entry);
+ recordData(bucketStart, bucketEnd, entry);
}
}
diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java
index 39a4d7b..d8e53d5 100644
--- a/core/java/android/net/NetworkTemplate.java
+++ b/core/java/android/net/NetworkTemplate.java
@@ -61,6 +61,13 @@
com.android.internal.R.array.config_data_usage_network_types);
}
+ private static boolean sForceAllNetworkTypes = false;
+
+ // @VisibleForTesting
+ public static void forceAllNetworkTypes() {
+ sForceAllNetworkTypes = true;
+ }
+
/**
* Template to match {@link ConnectivityManager#TYPE_MOBILE} networks with
* the given IMSI.
@@ -225,7 +232,7 @@
// TODO: consider matching against WiMAX subscriber identity
return true;
} else {
- return (contains(DATA_USAGE_NETWORK_TYPES, ident.mType)
+ return ((sForceAllNetworkTypes || contains(DATA_USAGE_NETWORK_TYPES, ident.mType))
&& Objects.equal(mSubscriberId, ident.mSubscriberId));
}
}
@@ -291,7 +298,7 @@
if (ident.mType == TYPE_WIMAX) {
return true;
} else {
- return contains(DATA_USAGE_NETWORK_TYPES, ident.mType);
+ return sForceAllNetworkTypes || contains(DATA_USAGE_NETWORK_TYPES, ident.mType);
}
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 4c86d9c..8b7ee0e 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4210,6 +4210,8 @@
public static final String NETSTATS_GLOBAL_ALERT_BYTES = "netstats_global_alert_bytes";
/** {@hide} */
public static final String NETSTATS_SAMPLE_ENABLED = "netstats_sample_enabled";
+ /** {@hide} */
+ public static final String NETSTATS_REPORT_XT_OVER_DEV = "netstats_report_xt_over_dev";
/** {@hide} */
public static final String NETSTATS_DEV_BUCKET_DURATION = "netstats_dev_bucket_duration";
diff --git a/services/java/com/android/server/net/NetworkStatsCollection.java b/services/java/com/android/server/net/NetworkStatsCollection.java
index c2e475a..9ddf011 100644
--- a/services/java/com/android/server/net/NetworkStatsCollection.java
+++ b/services/java/com/android/server/net/NetworkStatsCollection.java
@@ -71,7 +71,7 @@
private HashMap<Key, NetworkStatsHistory> mStats = Maps.newHashMap();
- private long mBucketDuration;
+ private final long mBucketDuration;
private long mStartMillis;
private long mEndMillis;
@@ -95,6 +95,18 @@
return mStartMillis;
}
+ /**
+ * Return first atomic bucket in this collection, which is more conservative
+ * than {@link #mStartMillis}.
+ */
+ public long getFirstAtomicBucketMillis() {
+ if (mStartMillis == Long.MAX_VALUE) {
+ return Long.MAX_VALUE;
+ } else {
+ return mStartMillis + mBucketDuration;
+ }
+ }
+
public long getEndMillis() {
return mEndMillis;
}
@@ -121,6 +133,15 @@
*/
public NetworkStatsHistory getHistory(
NetworkTemplate template, int uid, int set, int tag, int fields) {
+ return getHistory(template, uid, set, tag, fields, Long.MIN_VALUE, Long.MAX_VALUE);
+ }
+
+ /**
+ * Combine all {@link NetworkStatsHistory} in this collection which match
+ * the requested parameters.
+ */
+ public NetworkStatsHistory getHistory(
+ 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<Key, NetworkStatsHistory> entry : mStats.entrySet()) {
@@ -128,7 +149,7 @@
final boolean setMatches = set == SET_ALL || key.set == set;
if (key.uid == uid && setMatches && key.tag == tag
&& templateMatches(template, key.ident)) {
- combined.recordEntireHistory(entry.getValue());
+ combined.recordHistory(entry.getValue(), start, end);
}
}
return combined;
@@ -145,6 +166,9 @@
final NetworkStats.Entry entry = new NetworkStats.Entry();
NetworkStatsHistory.Entry historyEntry = null;
+ // shortcut when we know stats will be empty
+ if (start == end) return stats;
+
for (Map.Entry<Key, NetworkStatsHistory> mapEntry : mStats.entrySet()) {
final Key key = mapEntry.getKey();
if (templateMatches(template, key.ident)) {
@@ -175,8 +199,9 @@
*/
public void recordData(NetworkIdentitySet ident, int uid, int set, int tag, long start,
long end, NetworkStats.Entry entry) {
- noteRecordedHistory(start, end, entry.rxBytes + entry.txBytes);
- findOrCreateHistory(ident, uid, set, tag).recordData(start, end, entry);
+ final NetworkStatsHistory history = findOrCreateHistory(ident, uid, set, tag);
+ history.recordData(start, end, entry);
+ noteRecordedHistory(history.getStart(), history.getEnd(), entry.rxBytes + entry.txBytes);
}
/**
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
index 0e93b0a..ba122ec 100644
--- a/services/java/com/android/server/net/NetworkStatsService.java
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -44,6 +44,7 @@
import static android.provider.Settings.Secure.NETSTATS_DEV_ROTATE_AGE;
import static android.provider.Settings.Secure.NETSTATS_GLOBAL_ALERT_BYTES;
import static android.provider.Settings.Secure.NETSTATS_POLL_INTERVAL;
+import static android.provider.Settings.Secure.NETSTATS_REPORT_XT_OVER_DEV;
import static android.provider.Settings.Secure.NETSTATS_SAMPLE_ENABLED;
import static android.provider.Settings.Secure.NETSTATS_TIME_CACHE_MAX_AGE;
import static android.provider.Settings.Secure.NETSTATS_UID_BUCKET_DURATION;
@@ -177,6 +178,7 @@
public long getPollInterval();
public long getTimeCacheMaxAge();
public boolean getSampleEnabled();
+ public boolean getReportXtOverDev();
public static class Config {
public final long bucketDuration;
@@ -221,6 +223,8 @@
/** Cached {@link #mDevRecorder} stats. */
private NetworkStatsCollection mDevStatsCached;
+ /** Cached {@link #mXtRecorder} stats. */
+ private NetworkStatsCollection mXtStatsCached;
/** Current counter sets for each UID. */
private SparseIntArray mActiveUidCounterSet = new SparseIntArray();
@@ -295,6 +299,7 @@
// read historical network stats from disk, since policy service
// might need them right away.
mDevStatsCached = mDevRecorder.getOrLoadCompleteLocked();
+ mXtStatsCached = mXtRecorder.getOrLoadCompleteLocked();
// bootstrap initial stats to prevent double-counting later
bootstrapStatsLocked();
@@ -371,6 +376,7 @@
mUidTagRecorder = null;
mDevStatsCached = null;
+ mXtStatsCached = null;
mSystemReady = false;
}
@@ -469,12 +475,12 @@
@Override
public NetworkStats getSummaryForNetwork(
NetworkTemplate template, long start, long end) {
- return mDevStatsCached.getSummary(template, start, end);
+ return internalGetSummaryForNetwork(template, start, end);
}
@Override
public NetworkStatsHistory getHistoryForNetwork(NetworkTemplate template, int fields) {
- return mDevStatsCached.getHistory(template, UID_ALL, SET_ALL, TAG_NONE, fields);
+ return internalGetHistoryForNetwork(template, fields);
}
@Override
@@ -507,11 +513,56 @@
};
}
+ /**
+ * Return network summary, splicing between {@link #mDevStatsCached}
+ * and {@link #mXtStatsCached} when appropriate.
+ */
+ private NetworkStats internalGetSummaryForNetwork(
+ NetworkTemplate template, long start, long end) {
+ if (!mSettings.getReportXtOverDev()) {
+ // shortcut when XT reporting disabled
+ return mDevStatsCached.getSummary(template, start, end);
+ }
+
+ // splice stats between DEV and XT, switching over from DEV to XT at
+ // first atomic bucket.
+ final long firstAtomicBucket = mXtStatsCached.getFirstAtomicBucketMillis();
+ final NetworkStats dev = mDevStatsCached.getSummary(
+ template, Math.min(start, firstAtomicBucket), Math.min(end, firstAtomicBucket));
+ final NetworkStats xt = mXtStatsCached.getSummary(
+ template, Math.max(start, firstAtomicBucket), Math.max(end, firstAtomicBucket));
+
+ xt.combineAllValues(dev);
+ return xt;
+ }
+
+ /**
+ * Return network history, splicing between {@link #mDevStatsCached}
+ * and {@link #mXtStatsCached} when appropriate.
+ */
+ private NetworkStatsHistory internalGetHistoryForNetwork(NetworkTemplate template, int fields) {
+ if (!mSettings.getReportXtOverDev()) {
+ // shortcut when XT reporting disabled
+ return mDevStatsCached.getHistory(template, UID_ALL, SET_ALL, TAG_NONE, fields);
+ }
+
+ // splice stats between DEV and XT, switching over from DEV to XT at
+ // first atomic bucket.
+ final long firstAtomicBucket = mXtStatsCached.getFirstAtomicBucketMillis();
+ final NetworkStatsHistory dev = mDevStatsCached.getHistory(
+ template, UID_ALL, SET_ALL, TAG_NONE, fields, Long.MIN_VALUE, firstAtomicBucket);
+ final NetworkStatsHistory xt = mXtStatsCached.getHistory(
+ template, UID_ALL, SET_ALL, TAG_NONE, fields, firstAtomicBucket, Long.MAX_VALUE);
+
+ xt.recordEntireHistory(dev);
+ return xt;
+ }
+
@Override
public long getNetworkTotalBytes(NetworkTemplate template, long start, long end) {
mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
assertBandwidthControlEnabled();
- return mDevStatsCached.getSummary(template, start, end).getTotalBytes();
+ return internalGetSummaryForNetwork(template, start, end).getTotalBytes();
}
@Override
@@ -1190,6 +1241,10 @@
return getSecureBoolean(NETSTATS_SAMPLE_ENABLED, true);
}
@Override
+ public boolean getReportXtOverDev() {
+ return getSecureBoolean(NETSTATS_REPORT_XT_OVER_DEV, true);
+ }
+ @Override
public Config getDevConfig() {
return new Config(getSecureLong(NETSTATS_DEV_BUCKET_DURATION, HOUR_IN_MILLIS),
getSecureLong(NETSTATS_DEV_ROTATE_AGE, 15 * DAY_IN_MILLIS),
diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
index 332d198..cdc4d78 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
@@ -777,10 +777,80 @@
}
+ public void testReportXtOverDev() throws Exception {
+ // bring mobile network online
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkState(buildMobile3gState(IMSI_1));
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectNetworkStatsPoll();
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ verifyAndReset();
+
+ // create some traffic, but only for DEV, and across 1.5 buckets
+ incrementCurrentTime(90 * MINUTE_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummaryDev(new NetworkStats(getElapsedRealtime(), 1)
+ .addIfaceValues(TEST_IFACE, 6000L, 60L, 3000L, 30L));
+ expectNetworkStatsSummaryXt(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectNetworkStatsPoll();
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+ // verify service recorded history:
+ // 4000(dev) + 2000(dev)
+ assertNetworkTotal(sTemplateImsi1, 6000L, 60L, 3000L, 30L, 0);
+ verifyAndReset();
+
+ // create traffic on both DEV and XT, across two buckets
+ incrementCurrentTime(2 * HOUR_IN_MILLIS);
+ expectCurrentTime();
+ expectDefaultSettings();
+ expectNetworkStatsSummaryDev(new NetworkStats(getElapsedRealtime(), 1)
+ .addIfaceValues(TEST_IFACE, 6004L, 64L, 3004L, 34L));
+ expectNetworkStatsSummaryXt(new NetworkStats(getElapsedRealtime(), 1)
+ .addIfaceValues(TEST_IFACE, 10240L, 0L, 0L, 0L));
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ expectNetworkStatsPoll();
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+ // verify that we switching reporting at the first atomic XT bucket,
+ // which should give us:
+ // 4000(dev) + 2000(dev) + 1(dev) + 5120(xt) + 2560(xt)
+ assertNetworkTotal(sTemplateImsi1, 13681L, 61L, 3001L, 31L, 0);
+
+ // also test pure-DEV and pure-XT ranges
+ assertNetworkTotal(sTemplateImsi1, startTimeMillis(),
+ startTimeMillis() + 2 * HOUR_IN_MILLIS, 6001L, 61L, 3001L, 31L, 0);
+ assertNetworkTotal(sTemplateImsi1, startTimeMillis() + 2 * HOUR_IN_MILLIS,
+ startTimeMillis() + 4 * HOUR_IN_MILLIS, 7680L, 0L, 0L, 0L, 0);
+
+ verifyAndReset();
+ }
+
private void assertNetworkTotal(NetworkTemplate template, long rxBytes, long rxPackets,
long txBytes, long txPackets, int operations) throws Exception {
+ assertNetworkTotal(template, Long.MIN_VALUE, Long.MAX_VALUE, rxBytes, rxPackets, txBytes,
+ txPackets, operations);
+ }
+
+ private void assertNetworkTotal(NetworkTemplate template, long start, long end, long rxBytes,
+ long rxPackets, long txBytes, long txPackets, int operations) throws Exception {
+ // verify history API
final NetworkStatsHistory history = mSession.getHistoryForNetwork(template, FIELD_ALL);
- assertValues(history, Long.MIN_VALUE, Long.MAX_VALUE, rxBytes, rxPackets, txBytes,
+ assertValues(history, start, end, rxBytes, rxPackets, txBytes, txPackets, operations);
+
+ // verify summary API
+ final NetworkStats stats = mSession.getSummaryForNetwork(template, start, end);
+ assertValues(stats, IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes,
txPackets, operations);
}
@@ -791,10 +861,17 @@
private void assertUidTotal(NetworkTemplate template, int uid, int set, long rxBytes,
long rxPackets, long txBytes, long txPackets, int operations) throws Exception {
+ // verify history API
final NetworkStatsHistory history = mSession.getHistoryForUid(
template, uid, set, TAG_NONE, FIELD_ALL);
assertValues(history, Long.MIN_VALUE, Long.MAX_VALUE, rxBytes, rxPackets, txBytes,
txPackets, operations);
+
+ // verify summary API
+ final NetworkStats stats = mSession.getSummaryForAllUid(
+ template, Long.MIN_VALUE, Long.MAX_VALUE, false);
+ assertValues(stats, IFACE_ALL, uid, set, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets,
+ operations);
}
private void expectSystemReady() throws Exception {
@@ -819,7 +896,15 @@
}
private void expectNetworkStatsSummary(NetworkStats summary) throws Exception {
+ expectNetworkStatsSummaryDev(summary);
+ expectNetworkStatsSummaryXt(summary);
+ }
+
+ private void expectNetworkStatsSummaryDev(NetworkStats summary) throws Exception {
expect(mNetManager.getNetworkStatsSummaryDev()).andReturn(summary).atLeastOnce();
+ }
+
+ private void expectNetworkStatsSummaryXt(NetworkStats summary) throws Exception {
expect(mNetManager.getNetworkStatsSummaryXt()).andReturn(summary).atLeastOnce();
}
@@ -847,6 +932,7 @@
expect(mSettings.getPollInterval()).andReturn(HOUR_IN_MILLIS).anyTimes();
expect(mSettings.getTimeCacheMaxAge()).andReturn(DAY_IN_MILLIS).anyTimes();
expect(mSettings.getSampleEnabled()).andReturn(true).anyTimes();
+ expect(mSettings.getReportXtOverDev()).andReturn(true).anyTimes();
final Config config = new Config(bucketDuration, deleteAge, deleteAge);
expect(mSettings.getDevConfig()).andReturn(config).anyTimes();
@@ -885,8 +971,20 @@
private static void assertValues(NetworkStats stats, String iface, int uid, int set,
int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, int operations) {
- final int i = stats.findIndex(iface, uid, set, tag);
- final NetworkStats.Entry entry = stats.getValues(i, null);
+ final NetworkStats.Entry entry = new NetworkStats.Entry();
+ if (set == SET_DEFAULT || set == SET_ALL) {
+ final int i = stats.findIndex(iface, uid, SET_DEFAULT, tag);
+ if (i != -1) {
+ entry.add(stats.getValues(i, null));
+ }
+ }
+ if (set == SET_FOREGROUND || set == SET_ALL) {
+ final int i = stats.findIndex(iface, uid, SET_FOREGROUND, tag);
+ if (i != -1) {
+ entry.add(stats.getValues(i, null));
+ }
+ }
+
assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets);
assertEquals("unexpected txBytes", txBytes, entry.txBytes);
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkStatsCollectionTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkStatsCollectionTest.java
index 8634821..1a6c289 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkStatsCollectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkStatsCollectionTest.java
@@ -16,7 +16,11 @@
package com.android.server.net;
+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.NetworkTemplate.buildTemplateMobileAll;
+import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import android.content.res.Resources;
@@ -47,6 +51,14 @@
private static final String TEST_FILE = "test.bin";
private static final String TEST_IMSI = "310260000000000";
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ // ignore any device overlay while testing
+ NetworkTemplate.forceAllNetworkTypes();
+ }
+
public void testReadLegacyNetwork() throws Exception {
final File testFile = new File(getContext().getFilesDir(), TEST_FILE);
stageFile(R.raw.netstats_v1, testFile);
@@ -125,6 +137,20 @@
77017831L, 100995L, 35436758L, 92344L);
}
+ public void testStartEndAtomicBuckets() throws Exception {
+ final NetworkStatsCollection collection = new NetworkStatsCollection(HOUR_IN_MILLIS);
+
+ // record empty data straddling between buckets
+ final NetworkStats.Entry entry = new NetworkStats.Entry();
+ entry.rxBytes = 32;
+ collection.recordData(null, UID_ALL, SET_DEFAULT, TAG_NONE, 30 * MINUTE_IN_MILLIS,
+ 90 * MINUTE_IN_MILLIS, entry);
+
+ // assert that we report boundary in atomic buckets
+ assertEquals(0, collection.getStartMillis());
+ assertEquals(2 * HOUR_IN_MILLIS, collection.getEndMillis());
+ }
+
/**
* Copy a {@link Resources#openRawResource(int)} into {@link File} for
* testing purposes.