Include full network history around current time.
When requesting historical values with time ranges, always include
full values for buckets that span current time. (It doesn't make
sense to interpolate partial data.) Move getTotalData() to return
full Entry objects to prepare for packet counts.
Bug: 4691901
Change-Id: I717bd721be9f1d4a47c4121e46e07a56cb15bbf1
diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java
index b0930b2..8bd1738 100644
--- a/core/java/android/net/NetworkStatsHistory.java
+++ b/core/java/android/net/NetworkStatsHistory.java
@@ -124,6 +124,22 @@
return bucketDuration;
}
+ public long getStart() {
+ if (bucketCount > 0) {
+ return bucketStart[0];
+ } else {
+ return Long.MAX_VALUE;
+ }
+ }
+
+ public long getEnd() {
+ if (bucketCount > 0) {
+ return bucketStart[bucketCount - 1] + bucketDuration;
+ } else {
+ return Long.MIN_VALUE;
+ }
+ }
+
/**
* Return specific stats entry.
*/
@@ -253,9 +269,20 @@
* Return interpolated data usage across the requested range. Interpolates
* across buckets, so values may be rounded slightly.
*/
- public long[] getTotalData(long start, long end, long[] outTotal) {
- long rx = 0;
- long tx = 0;
+ public Entry getValues(long start, long end, Entry recycle) {
+ return getValues(start, end, Long.MAX_VALUE, recycle);
+ }
+
+ /**
+ * Return interpolated data usage across the requested range. Interpolates
+ * across buckets, so values may be rounded slightly.
+ */
+ public Entry getValues(long start, long end, long now, Entry recycle) {
+ final Entry entry = recycle != null ? recycle : new Entry();
+ entry.bucketStart = start;
+ entry.bucketDuration = end - start;
+ entry.rxBytes = 0;
+ entry.txBytes = 0;
for (int i = bucketCount - 1; i >= 0; i--) {
final long curStart = bucketStart[i];
@@ -266,19 +293,19 @@
// bucket is newer than record; keep looking
if (curStart > end) continue;
+ // include full value for active buckets, otherwise only fractional
+ final boolean activeBucket = curStart < now && curEnd > now;
final long overlap = Math.min(curEnd, end) - Math.max(curStart, start);
- if (overlap > 0) {
- rx += this.rxBytes[i] * overlap / bucketDuration;
- tx += this.txBytes[i] * overlap / bucketDuration;
+ if (activeBucket || overlap == bucketDuration) {
+ entry.rxBytes += rxBytes[i];
+ entry.txBytes += txBytes[i];
+ } else if (overlap > 0) {
+ entry.rxBytes += rxBytes[i] * overlap / bucketDuration;
+ entry.txBytes += txBytes[i] * overlap / bucketDuration;
}
}
- if (outTotal == null || outTotal.length != 2) {
- outTotal = new long[2];
- }
- outTotal[0] = rx;
- outTotal[1] = tx;
- return outTotal;
+ return entry;
}
/**
diff --git a/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java b/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java
index 16bb000..9403d95 100644
--- a/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java
+++ b/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java
@@ -55,7 +55,7 @@
stats.recordData(TEST_START, TEST_START + SECOND_IN_MILLIS, 1024L, 2048L);
assertEquals(1, stats.size());
- assertEntry(stats, 0, 1024L, 2048L);
+ assertValues(stats, 0, 1024L, 2048L);
}
public void testRecordEqualBuckets() throws Exception {
@@ -67,8 +67,8 @@
stats.recordData(recordStart, recordStart + bucketDuration, 1024L, 128L);
assertEquals(2, stats.size());
- assertEntry(stats, 0, 512L, 64L);
- assertEntry(stats, 1, 512L, 64L);
+ assertValues(stats, 0, 512L, 64L);
+ assertValues(stats, 1, 512L, 64L);
}
public void testRecordTouchingBuckets() throws Exception {
@@ -83,11 +83,11 @@
assertEquals(3, stats.size());
// first bucket should have (1/20 of value)
- assertEntry(stats, 0, 50L, 250L);
+ assertValues(stats, 0, 50L, 250L);
// second bucket should have (15/20 of value)
- assertEntry(stats, 1, 750L, 3750L);
+ assertValues(stats, 1, 750L, 3750L);
// final bucket should have (4/20 of value)
- assertEntry(stats, 2, 200L, 1000L);
+ assertValues(stats, 2, 200L, 1000L);
}
public void testRecordGapBuckets() throws Exception {
@@ -102,8 +102,8 @@
// we should have two buckets, far apart from each other
assertEquals(2, stats.size());
- assertEntry(stats, 0, 128L, 256L);
- assertEntry(stats, 1, 64L, 512L);
+ assertValues(stats, 0, 128L, 256L);
+ assertValues(stats, 1, 64L, 512L);
// now record something in middle, spread across two buckets
final long middleStart = TEST_START + DAY_IN_MILLIS;
@@ -112,10 +112,10 @@
// now should have four buckets, with new record in middle two buckets
assertEquals(4, stats.size());
- assertEntry(stats, 0, 128L, 256L);
- assertEntry(stats, 1, 1024L, 1024L);
- assertEntry(stats, 2, 1024L, 1024L);
- assertEntry(stats, 3, 64L, 512L);
+ assertValues(stats, 0, 128L, 256L);
+ assertValues(stats, 1, 1024L, 1024L);
+ assertValues(stats, 2, 1024L, 1024L);
+ assertValues(stats, 3, 64L, 512L);
}
public void testRecordOverlapBuckets() throws Exception {
@@ -129,13 +129,11 @@
// should have two buckets, with some data mixed together
assertEquals(2, stats.size());
- assertEntry(stats, 0, 768L, 768L);
- assertEntry(stats, 1, 512L, 512L);
+ assertValues(stats, 0, 768L, 768L);
+ assertValues(stats, 1, 512L, 512L);
}
public void testRecordEntireGapIdentical() throws Exception {
- final long[] total = new long[2];
-
// first, create two separate histories far apart
final NetworkStatsHistory stats1 = new NetworkStatsHistory(HOUR_IN_MILLIS);
stats1.recordData(TEST_START, TEST_START + 2 * HOUR_IN_MILLIS, 2000L, 1000L);
@@ -150,19 +148,16 @@
stats.recordEntireHistory(stats2);
// first verify that totals match up
- stats.getTotalData(TEST_START - WEEK_IN_MILLIS, TEST_START + WEEK_IN_MILLIS, total);
- assertTotalEquals(total, 3000L, 1500L);
+ assertValues(stats, TEST_START - WEEK_IN_MILLIS, TEST_START + WEEK_IN_MILLIS, 3000L, 1500L);
// now inspect internal buckets
- assertEntry(stats, 0, 1000L, 500L);
- assertEntry(stats, 1, 1000L, 500L);
- assertEntry(stats, 2, 500L, 250L);
- assertEntry(stats, 3, 500L, 250L);
+ assertValues(stats, 0, 1000L, 500L);
+ assertValues(stats, 1, 1000L, 500L);
+ assertValues(stats, 2, 500L, 250L);
+ assertValues(stats, 3, 500L, 250L);
}
public void testRecordEntireOverlapVaryingBuckets() throws Exception {
- final long[] total = new long[2];
-
// create history just over hour bucket boundary
final NetworkStatsHistory stats1 = new NetworkStatsHistory(HOUR_IN_MILLIS);
stats1.recordData(TEST_START, TEST_START + MINUTE_IN_MILLIS * 60, 600L, 600L);
@@ -177,17 +172,16 @@
stats.recordEntireHistory(stats2);
// first verify that totals match up
- stats.getTotalData(TEST_START - WEEK_IN_MILLIS, TEST_START + WEEK_IN_MILLIS, total);
- assertTotalEquals(total, 650L, 650L);
+ assertValues(stats, TEST_START - WEEK_IN_MILLIS, TEST_START + WEEK_IN_MILLIS, 650L, 650L);
// now inspect internal buckets
- assertEntry(stats, 0, 10L, 10L);
- assertEntry(stats, 1, 20L, 20L);
- assertEntry(stats, 2, 20L, 20L);
- assertEntry(stats, 3, 20L, 20L);
- assertEntry(stats, 4, 20L, 20L);
- assertEntry(stats, 5, 20L, 20L);
- assertEntry(stats, 6, 10L, 10L);
+ assertValues(stats, 0, 10L, 10L);
+ assertValues(stats, 1, 20L, 20L);
+ assertValues(stats, 2, 20L, 20L);
+ assertValues(stats, 3, 20L, 20L);
+ assertValues(stats, 4, 20L, 20L);
+ assertValues(stats, 5, 20L, 20L);
+ assertValues(stats, 6, 10L, 10L);
// now combine using 15min buckets
stats = new NetworkStatsHistory(HOUR_IN_MILLIS / 4);
@@ -195,14 +189,13 @@
stats.recordEntireHistory(stats2);
// first verify that totals match up
- stats.getTotalData(TEST_START - WEEK_IN_MILLIS, TEST_START + WEEK_IN_MILLIS, total);
- assertTotalEquals(total, 650L, 650L);
+ assertValues(stats, TEST_START - WEEK_IN_MILLIS, TEST_START + WEEK_IN_MILLIS, 650L, 650L);
// and inspect buckets
- assertEntry(stats, 0, 200L, 200L);
- assertEntry(stats, 1, 150L, 150L);
- assertEntry(stats, 2, 150L, 150L);
- assertEntry(stats, 3, 150L, 150L);
+ assertValues(stats, 0, 200L, 200L);
+ assertValues(stats, 1, 150L, 150L);
+ assertValues(stats, 2, 150L, 150L);
+ assertValues(stats, 3, 150L, 150L);
}
public void testRemove() throws Exception {
@@ -241,27 +234,20 @@
// record uniform data across day
stats.recordData(TEST_START, TEST_START + DAY_IN_MILLIS, 2400L, 4800L);
- final long[] total = new long[2];
-
// verify that total outside range is 0
- stats.getTotalData(TEST_START - WEEK_IN_MILLIS, TEST_START - DAY_IN_MILLIS, total);
- assertTotalEquals(total, 0, 0);
+ assertValues(stats, TEST_START - WEEK_IN_MILLIS, TEST_START - DAY_IN_MILLIS, 0L, 0L);
// verify total in first hour
- stats.getTotalData(TEST_START, TEST_START + HOUR_IN_MILLIS, total);
- assertTotalEquals(total, 100, 200);
+ assertValues(stats, TEST_START, TEST_START + HOUR_IN_MILLIS, 100L, 200L);
// verify total across 1.5 hours
- stats.getTotalData(TEST_START, TEST_START + (long) (1.5 * HOUR_IN_MILLIS), total);
- assertTotalEquals(total, 150, 300);
+ assertValues(stats, TEST_START, TEST_START + (long) (1.5 * HOUR_IN_MILLIS), 150L, 300L);
// verify total beyond end
- stats.getTotalData(TEST_START + (23 * HOUR_IN_MILLIS), TEST_START + WEEK_IN_MILLIS, total);
- assertTotalEquals(total, 100, 200);
+ assertValues(stats, TEST_START + (23 * HOUR_IN_MILLIS), TEST_START + WEEK_IN_MILLIS, 100L, 200L);
// verify everything total
- stats.getTotalData(TEST_START - WEEK_IN_MILLIS, TEST_START + WEEK_IN_MILLIS, total);
- assertTotalEquals(total, 2400, 4800);
+ assertValues(stats, TEST_START - WEEK_IN_MILLIS, TEST_START + WEEK_IN_MILLIS, 2400L, 4800L);
}
@@ -302,16 +288,18 @@
}
}
- private static void assertTotalEquals(long[] total, long rxBytes, long txBytes) {
- assertEquals("unexpected rxBytes", rxBytes, total[0]);
- assertEquals("unexpected txBytes", txBytes, total[1]);
- }
-
- private static void assertEntry(
+ private static void assertValues(
NetworkStatsHistory stats, int index, long rxBytes, long txBytes) {
final NetworkStatsHistory.Entry entry = stats.getValues(index, null);
assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
assertEquals("unexpected txBytes", txBytes, entry.txBytes);
}
+ private static void assertValues(
+ NetworkStatsHistory stats, long start, long end, long rxBytes, long txBytes) {
+ final NetworkStatsHistory.Entry entry = stats.getValues(start, end, null);
+ assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
+ assertEquals("unexpected txBytes", txBytes, entry.txBytes);
+ }
+
}
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
index 872438c..54e94db 100644
--- a/services/java/com/android/server/net/NetworkStatsService.java
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -313,21 +313,24 @@
mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
synchronized (mStatsLock) {
+ // use system clock to be externally consistent
+ final long now = System.currentTimeMillis();
+
final NetworkStats stats = new NetworkStats(end - start, 1);
final NetworkStats.Entry entry = new NetworkStats.Entry();
- long[] total = new long[2];
+ NetworkStatsHistory.Entry historyEntry = null;
// combine total from all interfaces that match template
for (NetworkIdentitySet ident : mNetworkStats.keySet()) {
if (templateMatches(template, ident)) {
final NetworkStatsHistory history = mNetworkStats.get(ident);
- total = history.getTotalData(start, end, total);
+ historyEntry = history.getValues(start, end, now, historyEntry);
entry.iface = IFACE_ALL;
entry.uid = UID_ALL;
entry.tag = TAG_NONE;
- entry.rxBytes = total[0];
- entry.txBytes = total[1];
+ entry.rxBytes = historyEntry.rxBytes;
+ entry.txBytes = historyEntry.txBytes;
stats.combineValues(entry);
}
@@ -345,9 +348,12 @@
synchronized (mStatsLock) {
ensureUidStatsLoadedLocked();
+ // use system clock to be externally consistent
+ final long now = System.currentTimeMillis();
+
final NetworkStats stats = new NetworkStats(end - start, 24);
final NetworkStats.Entry entry = new NetworkStats.Entry();
- long[] total = new long[2];
+ NetworkStatsHistory.Entry historyEntry = null;
for (NetworkIdentitySet ident : mUidStats.keySet()) {
if (templateMatches(template, ident)) {
@@ -361,13 +367,13 @@
// other tags when requested.
if (tag == TAG_NONE || includeTags) {
final NetworkStatsHistory history = uidStats.valueAt(i);
- total = history.getTotalData(start, end, total);
+ historyEntry = history.getValues(start, end, now, historyEntry);
entry.iface = IFACE_ALL;
entry.uid = uid;
entry.tag = tag;
- entry.rxBytes = total[0];
- entry.txBytes = total[1];
+ entry.rxBytes = historyEntry.rxBytes;
+ entry.txBytes = historyEntry.txBytes;
if (entry.rxBytes > 0 || entry.txBytes > 0) {
stats.combineValues(entry);
@@ -425,6 +431,7 @@
// broadcast.
final int uid = intent.getIntExtra(EXTRA_UID, 0);
synchronized (mStatsLock) {
+ // TODO: perform one last stats poll for UID
removeUidLocked(uid);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
index 36b3b82..ac74063 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
@@ -264,7 +264,6 @@
public void testStatsBucketResize() throws Exception {
long elapsedRealtime = 0;
NetworkStatsHistory history = null;
- long[] total = null;
assertStatsFilesExist(false);
@@ -292,9 +291,7 @@
// verify service recorded history
history = mService.getHistoryForNetwork(new NetworkTemplate(MATCH_WIFI, null));
- total = history.getTotalData(Long.MIN_VALUE, Long.MAX_VALUE, null);
- assertEquals(512L, total[0]);
- assertEquals(512L, total[1]);
+ assertValues(history, Long.MIN_VALUE, Long.MAX_VALUE, 512L, 512L);
assertEquals(HOUR_IN_MILLIS, history.getBucketDuration());
assertEquals(2, history.size());
verifyAndReset();
@@ -311,9 +308,7 @@
// verify identical stats, but spread across 4 buckets now
history = mService.getHistoryForNetwork(new NetworkTemplate(MATCH_WIFI, null));
- total = history.getTotalData(Long.MIN_VALUE, Long.MAX_VALUE, null);
- assertEquals(512L, total[0]);
- assertEquals(512L, total[1]);
+ assertValues(history, Long.MIN_VALUE, Long.MAX_VALUE, 512L, 512L);
assertEquals(30 * MINUTE_IN_MILLIS, history.getBucketDuration());
assertEquals(4, history.size());
verifyAndReset();
@@ -575,32 +570,28 @@
NetworkStats stats = mService.getSummaryForAllUid(
sTemplateWifi, Long.MIN_VALUE, Long.MAX_VALUE, true);
assertEquals(3, stats.size());
- assertEntry(stats, 0, IFACE_ALL, UID_RED, TAG_NONE, 50L, 5L, 50L, 5L);
- assertEntry(stats, 1, IFACE_ALL, UID_RED, 0xF00D, 10L, 1L, 10L, 1L);
- assertEntry(stats, 2, IFACE_ALL, UID_BLUE, TAG_NONE, 2048L, 16L, 1024L, 8L);
+ assertValues(stats, 0, IFACE_ALL, UID_RED, TAG_NONE, 50L, 5L, 50L, 5L);
+ assertValues(stats, 1, IFACE_ALL, UID_RED, 0xF00D, 10L, 1L, 10L, 1L);
+ assertValues(stats, 2, IFACE_ALL, UID_BLUE, TAG_NONE, 2048L, 16L, 1024L, 8L);
// now verify that recent history only contains one uid
final long currentTime = TEST_START + elapsedRealtime;
stats = mService.getSummaryForAllUid(
sTemplateWifi, currentTime - HOUR_IN_MILLIS, currentTime, true);
assertEquals(1, stats.size());
- assertEntry(stats, 0, IFACE_ALL, UID_BLUE, TAG_NONE, 1024L, 8L, 512L, 4L);
+ assertValues(stats, 0, IFACE_ALL, UID_BLUE, TAG_NONE, 1024L, 8L, 512L, 4L);
verifyAndReset();
}
- private void assertNetworkTotal(NetworkTemplate template, long rx, long tx) {
+ private void assertNetworkTotal(NetworkTemplate template, long rxBytes, long txBytes) {
final NetworkStatsHistory history = mService.getHistoryForNetwork(template);
- final long[] total = history.getTotalData(Long.MIN_VALUE, Long.MAX_VALUE, null);
- assertEquals(rx, total[0]);
- assertEquals(tx, total[1]);
+ assertValues(history, Long.MIN_VALUE, Long.MAX_VALUE, rxBytes, txBytes);
}
- private void assertUidTotal(NetworkTemplate template, int uid, long rx, long tx) {
+ private void assertUidTotal(NetworkTemplate template, int uid, long rxBytes, long txBytes) {
final NetworkStatsHistory history = mService.getHistoryForUid(template, uid, TAG_NONE);
- final long[] total = history.getTotalData(Long.MIN_VALUE, Long.MAX_VALUE, null);
- assertEquals(rx, total[0]);
- assertEquals(tx, total[1]);
+ assertValues(history, Long.MIN_VALUE, Long.MAX_VALUE, rxBytes, txBytes);
}
private void expectSystemReady() throws Exception {
@@ -660,7 +651,7 @@
}
}
- private static void assertEntry(NetworkStats stats, int i, String iface, int uid, int tag,
+ private static void assertValues(NetworkStats stats, int i, String iface, int uid, int tag,
long rxBytes, long rxPackets, long txBytes, long txPackets) {
final NetworkStats.Entry entry = stats.getValues(i, null);
assertEquals(iface, entry.iface);
@@ -673,6 +664,13 @@
// assertEquals(txPackets, entry.txPackets);
}
+ private static void assertValues(
+ NetworkStatsHistory stats, long start, long end, long rxBytes, long txBytes) {
+ final NetworkStatsHistory.Entry entry = stats.getValues(start, end, null);
+ assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
+ assertEquals("unexpected txBytes", txBytes, entry.txBytes);
+ }
+
private static NetworkState buildWifiState() {
final NetworkInfo info = new NetworkInfo(TYPE_WIFI, 0, null, null);
info.setDetailedState(DetailedState.CONNECTED, null, null);