Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 19862bf5 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Compute range-based usage in NetworkStatsHistory.

When given a start/end range, interpolate between buckets to return
the total network usage.  Used to summarize detailed UID stats.  Method
to combine NetworkStatsHistory regardless of bucket size.  Used to
combine all histories matching a template.

Added tests for both methods.

Change-Id: Ia463910c0ecf7cf08dcf97c658ad99742bd6b882
parent 61ee0bbb
Loading
Loading
Loading
Loading
+40 −0
Original line number Diff line number Diff line
@@ -133,6 +133,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.
     */
@@ -202,6 +214,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
     */
+109 −2
Original line number Diff line number Diff line
@@ -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]);
+16 −3
Original line number Diff line number Diff line
@@ -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();
        }
    }

    /**