diff options
4 files changed, 117 insertions, 97 deletions
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 @@ public class NetworkStatsHistory implements Parcelable { 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 @@ public class NetworkStatsHistory implements Parcelable { * 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 @@ public class NetworkStatsHistory implements Parcelable { // 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 @@ public class NetworkStatsHistoryTest extends TestCase { 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 @@ public class NetworkStatsHistoryTest extends TestCase { 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 @@ public class NetworkStatsHistoryTest extends TestCase { 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 @@ public class NetworkStatsHistoryTest extends TestCase { // 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 @@ public class NetworkStatsHistoryTest extends TestCase { // 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 @@ public class NetworkStatsHistoryTest extends TestCase { // 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 @@ public class NetworkStatsHistoryTest extends TestCase { 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 @@ public class NetworkStatsHistoryTest extends TestCase { 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 @@ public class NetworkStatsHistoryTest extends TestCase { 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 @@ public class NetworkStatsHistoryTest extends TestCase { // 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 @@ public class NetworkStatsHistoryTest extends TestCase { } } - 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // 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 class NetworkStatsServiceTest extends AndroidTestCase { public void testStatsBucketResize() throws Exception { long elapsedRealtime = 0; NetworkStatsHistory history = null; - long[] total = null; assertStatsFilesExist(false); @@ -292,9 +291,7 @@ public class NetworkStatsServiceTest extends AndroidTestCase { // 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 @@ public class NetworkStatsServiceTest extends AndroidTestCase { // 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 @@ public class NetworkStatsServiceTest extends AndroidTestCase { 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 @@ public class NetworkStatsServiceTest extends AndroidTestCase { } } - 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 @@ public class NetworkStatsServiceTest extends AndroidTestCase { // 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); |