diff options
3 files changed, 165 insertions, 5 deletions
diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java index 45d8e27..5edbf58 100644 --- a/core/java/android/net/NetworkStatsHistory.java +++ b/core/java/android/net/NetworkStatsHistory.java @@ -134,6 +134,18 @@ public class NetworkStatsHistory implements Parcelable { } /** + * Record an entire {@link NetworkStatsHistory} into this history. Usually + * for combining together stats for external reporting. + */ + public void recordEntireHistory(NetworkStatsHistory input) { + for (int i = 0; i < input.bucketCount; i++) { + final long start = input.bucketStart[i]; + final long end = start + input.bucketDuration; + recordData(start, end, input.rx[i], input.tx[i]); + } + } + + /** * Ensure that buckets exist for given time range, creating as needed. */ private void ensureBuckets(long start, long end) { @@ -203,6 +215,34 @@ public class NetworkStatsHistory implements Parcelable { } /** + * Return interpolated data usage across the requested range. Interpolates + * across buckets, so values may be rounded slightly. + */ + public void getTotalData(long start, long end, long[] outTotal) { + long rx = 0; + long tx = 0; + + for (int i = bucketCount - 1; i >= 0; i--) { + final long curStart = bucketStart[i]; + final long curEnd = curStart + bucketDuration; + + // bucket is older than record; we're finished + if (curEnd < start) break; + // bucket is newer than record; keep looking + if (curStart > end) continue; + + final long overlap = Math.min(curEnd, end) - Math.max(curStart, start); + if (overlap > 0) { + rx += this.rx[i] * overlap / bucketDuration; + tx += this.tx[i] * overlap / bucketDuration; + } + } + + outTotal[0] = rx; + outTotal[1] = tx; + } + + /** * @deprecated only for temporary testing */ @Deprecated diff --git a/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java b/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java index 69bbcd7..0b72c3c 100644 --- a/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java +++ b/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java @@ -133,9 +133,80 @@ public class NetworkStatsHistoryTest extends TestCase { assertBucket(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); + + final long TEST_START_2 = TEST_START + DAY_IN_MILLIS; + final NetworkStatsHistory stats2 = new NetworkStatsHistory(HOUR_IN_MILLIS); + stats2.recordData(TEST_START_2, TEST_START_2 + 2 * HOUR_IN_MILLIS, 1000L, 500L); + + // combine together with identical bucket size + stats = new NetworkStatsHistory(HOUR_IN_MILLIS); + stats.recordEntireHistory(stats1); + 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); + + // now inspect internal buckets + assertBucket(stats, 0, 1000L, 500L); + assertBucket(stats, 1, 1000L, 500L); + assertBucket(stats, 2, 500L, 250L); + assertBucket(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); + + final long TEST_START_2 = TEST_START + MINUTE_IN_MILLIS; + final NetworkStatsHistory stats2 = new NetworkStatsHistory(MINUTE_IN_MILLIS); + stats2.recordData(TEST_START_2, TEST_START_2 + MINUTE_IN_MILLIS * 5, 50L, 50L); + + // combine together with minute bucket size + stats = new NetworkStatsHistory(MINUTE_IN_MILLIS); + stats.recordEntireHistory(stats1); + 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); + + // now inspect internal buckets + assertBucket(stats, 0, 10L, 10L); + assertBucket(stats, 1, 20L, 20L); + assertBucket(stats, 2, 20L, 20L); + assertBucket(stats, 3, 20L, 20L); + assertBucket(stats, 4, 20L, 20L); + assertBucket(stats, 5, 20L, 20L); + assertBucket(stats, 6, 10L, 10L); + + // now combine using 15min buckets + stats = new NetworkStatsHistory(HOUR_IN_MILLIS / 4); + stats.recordEntireHistory(stats1); + 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); + + // and inspect buckets + assertBucket(stats, 0, 200L, 200L); + assertBucket(stats, 1, 150L, 150L); + assertBucket(stats, 2, 150L, 150L); + assertBucket(stats, 3, 150L, 150L); + } + public void testRemove() throws Exception { - final long BUCKET_SIZE = HOUR_IN_MILLIS; - stats = new NetworkStatsHistory(BUCKET_SIZE); + stats = new NetworkStatsHistory(HOUR_IN_MILLIS); // record some data across 24 buckets stats.recordData(TEST_START, TEST_START + DAY_IN_MILLIS, 24L, 24L); @@ -163,6 +234,37 @@ public class NetworkStatsHistoryTest extends TestCase { assertEquals(0, stats.bucketCount); } + public void testTotalData() throws Exception { + final long BUCKET_SIZE = HOUR_IN_MILLIS; + stats = new NetworkStatsHistory(BUCKET_SIZE); + + // 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); + + // verify total in first hour + stats.getTotalData(TEST_START, TEST_START + HOUR_IN_MILLIS, total); + assertTotalEquals(total, 100, 200); + + // verify total across 1.5 hours + stats.getTotalData(TEST_START, TEST_START + (long) (1.5 * HOUR_IN_MILLIS), total); + assertTotalEquals(total, 150, 300); + + // verify total beyond end + stats.getTotalData(TEST_START + (23 * HOUR_IN_MILLIS), TEST_START + WEEK_IN_MILLIS, total); + assertTotalEquals(total, 100, 200); + + // verify everything total + stats.getTotalData(TEST_START - WEEK_IN_MILLIS, TEST_START + WEEK_IN_MILLIS, total); + assertTotalEquals(total, 2400, 4800); + + } + @Suppress public void testFuzzing() throws Exception { try { @@ -196,6 +298,11 @@ public class NetworkStatsHistoryTest extends TestCase { } } + private static void assertTotalEquals(long[] total, long rx, long tx) { + assertEquals("unexpected rx", rx, total[0]); + assertEquals("unexpected tx", tx, total[1]); + } + private static void assertBucket(NetworkStatsHistory stats, int index, long rx, long tx) { assertEquals("unexpected rx", rx, stats.rx[index]); assertEquals("unexpected tx", tx, stats.tx[index]); diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java index 967e491..3892de8 100644 --- a/services/java/com/android/server/net/NetworkStatsService.java +++ b/services/java/com/android/server/net/NetworkStatsService.java @@ -210,7 +210,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { for (InterfaceIdentity ident : mSummaryStats.keySet()) { final NetworkStatsHistory history = mSummaryStats.get(ident); if (ident.matchesTemplate(networkTemplate, subscriberId)) { - // TODO: combine all matching history data into a single history + combined.recordEntireHistory(history); } } return combined; @@ -231,8 +231,21 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // TODO: create relaxed permission for reading stats mContext.enforceCallingOrSelfPermission(UPDATE_DEVICE_STATS, TAG); - // TODO: total UID-granularity usage between time range - return null; + // TODO: apply networktemplate once granular uid stats are stored. + + synchronized (mStatsLock) { + final int size = mDetailStats.size(); + final NetworkStats.Builder stats = new NetworkStats.Builder(end - start, size); + + final long[] total = new long[2]; + for (int i = 0; i < size; i++) { + final int uid = mDetailStats.keyAt(i); + final NetworkStatsHistory history = mDetailStats.valueAt(i); + history.getTotalData(start, end, total); + stats.addEntry(IFACE_ALL, uid, total[0], total[1]); + } + return stats.build(); + } } /** |