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

Commit 39ebc219 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Persist UID stats, lazy loading, resize buckets.

Persisting detailed UID stats in separate "netstats_detail.bin" file
to enable different schedules for summary and detail polling.  Only
load detailed UID history on demand, since it's not needed during
boot.  Add test to verify UID stats are persisted across simulated
reboot.

Move external settings into well-named interface, which is still
backed by Settings.Secure.  During periodic poll events, resize any
history to match current bucket duration setting.  Test to verify.

Change-Id: I6366f3583a591f8ba859b0e5987daf8cafa4e95a
parent 13010be7
Loading
Loading
Loading
Loading
+4 −4
Original line number Diff line number Diff line
@@ -3800,13 +3800,13 @@ public final class Settings {
        /** {@hide} */
        public static final String NETSTATS_PERSIST_THRESHOLD = "netstats_persist_threshold";
        /** {@hide} */
        public static final String NETSTATS_SUMMARY_BUCKET_DURATION = "netstats_summary_bucket_duration";
        public static final String NETSTATS_NETWORK_BUCKET_DURATION = "netstats_network_bucket_duration";
        /** {@hide} */
        public static final String NETSTATS_SUMMARY_MAX_HISTORY = "netstats_summary_max_history";
        public static final String NETSTATS_NETWORK_MAX_HISTORY = "netstats_network_max_history";
        /** {@hide} */
        public static final String NETSTATS_DETAIL_BUCKET_DURATION = "netstats_detail_bucket_duration";
        public static final String NETSTATS_UID_BUCKET_DURATION = "netstats_uid_bucket_duration";
        /** {@hide} */
        public static final String NETSTATS_DETAIL_MAX_HISTORY = "netstats_detail_max_history";
        public static final String NETSTATS_UID_MAX_HISTORY = "netstats_uid_max_history";

        /**
         * @hide
+298 −137

File changed.

Preview size limit exceeded, changes collapsed.

+173 −61
Original line number Diff line number Diff line
@@ -18,10 +18,13 @@ package com.android.server;

import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.NetworkStats.IFACE_ALL;
import static android.net.NetworkStats.UID_ALL;
import static android.net.TrafficStats.TEMPLATE_WIFI;
import static android.text.format.DateUtils.DAY_IN_MILLIS;
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static android.text.format.DateUtils.WEEK_IN_MILLIS;
import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_POLL;
import static org.easymock.EasyMock.anyLong;
import static org.easymock.EasyMock.createMock;
@@ -47,6 +50,7 @@ import android.test.suitebuilder.annotation.LargeTest;
import android.util.TrustedTime;

import com.android.server.net.NetworkStatsService;
import com.android.server.net.NetworkStatsService.NetworkStatsSettings;

import org.easymock.EasyMock;

@@ -62,12 +66,16 @@ public class NetworkStatsServiceTest extends AndroidTestCase {
    private static final String TEST_IFACE = "test0";
    private static final long TEST_START = 1194220800000L;

    private static final int TEST_UID_1 = 1001;
    private static final int TEST_UID_2 = 1002;

    private BroadcastInterceptingContext mServiceContext;
    private File mStatsDir;

    private INetworkManagementService mNetManager;
    private IAlarmManager mAlarmManager;
    private TrustedTime mTime;
    private NetworkStatsSettings mSettings;
    private IConnectivityManager mConnManager;

    private NetworkStatsService mService;
@@ -82,12 +90,14 @@ public class NetworkStatsServiceTest extends AndroidTestCase {
        mNetManager = createMock(INetworkManagementService.class);
        mAlarmManager = createMock(IAlarmManager.class);
        mTime = createMock(TrustedTime.class);
        mSettings = createMock(NetworkStatsSettings.class);
        mConnManager = createMock(IConnectivityManager.class);

        mService = new NetworkStatsService(
                mServiceContext, mNetManager, mAlarmManager, mTime, mStatsDir);
                mServiceContext, mNetManager, mAlarmManager, mTime, mStatsDir, mSettings);
        mService.bindConnectivityManager(mConnManager);

        expectDefaultSettings();
        expectSystemReady();

        replay();
@@ -114,115 +124,93 @@ public class NetworkStatsServiceTest extends AndroidTestCase {
        super.tearDown();
    }

    private static NetworkState buildWifi() {
        final NetworkInfo info = new NetworkInfo(TYPE_WIFI, 0, null, null);
        info.setDetailedState(DetailedState.CONNECTED, null, null);
        final LinkProperties prop = new LinkProperties();
        prop.setInterfaceName(TEST_IFACE);
        return new NetworkState(info, prop, null);
    }

    public void testHistoryForWifi() throws Exception {
    public void testSummaryStatsWifi() throws Exception {
        long elapsedRealtime = 0;
        NetworkState[] state = null;
        NetworkStats stats = null;
        NetworkStats detail = null;

        // pretend that wifi network comes online; service should ask about full
        // network state, and poll any existing interfaces before updating.
        state = new NetworkState[] { buildWifi() };
        stats = new NetworkStats.Builder(elapsedRealtime, 0).build();
        detail = new NetworkStats.Builder(elapsedRealtime, 0).build();

        expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce();
        expect(mNetManager.getNetworkStatsSummary()).andReturn(stats).atLeastOnce();
        expect(mNetManager.getNetworkStatsDetail()).andReturn(detail).atLeastOnce();
        expectTime(TEST_START + elapsedRealtime);
        expectDefaultSettings();
        expectNetworkState(buildWifiState());
        expectNetworkStatsSummary(buildEmptyStats(elapsedRealtime));

        replay();
        mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
        verifyAndReset();

        // verify service has empty history for wifi
        assertNetworkTotal(TEMPLATE_WIFI, 0L, 0L);
        verifyAndReset();

        // modify some number on wifi, and trigger poll event
        elapsedRealtime += HOUR_IN_MILLIS;
        stats = new NetworkStats.Builder(elapsedRealtime, 1).addEntry(
                TEST_IFACE, UID_ALL, 1024L, 2048L).build();

        expect(mNetManager.getNetworkStatsSummary()).andReturn(stats).atLeastOnce();
        expect(mNetManager.getNetworkStatsDetail()).andReturn(detail).atLeastOnce();
        expectTime(TEST_START + elapsedRealtime);
        expectDefaultSettings();
        expectNetworkStatsSummary(new NetworkStats.Builder(elapsedRealtime, 1)
                .addEntry(TEST_IFACE, UID_ALL, 1024L, 2048L).build());
        expectNetworkStatsDetail(buildEmptyStats(elapsedRealtime));

        replay();
        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
        verifyAndReset();

        // verify service recorded history
        assertNetworkTotal(TEMPLATE_WIFI, 1024L, 2048L);
        verifyAndReset();

        // and bump forward again, with counters going higher. this is
        // important, since polling should correctly subtract last snapshot.
        elapsedRealtime += DAY_IN_MILLIS;
        stats = new NetworkStats.Builder(elapsedRealtime, 1).addEntry(
                TEST_IFACE, UID_ALL, 4096L, 8192L).build();

        expect(mNetManager.getNetworkStatsSummary()).andReturn(stats).atLeastOnce();
        expect(mNetManager.getNetworkStatsDetail()).andReturn(detail).atLeastOnce();
        expectTime(TEST_START + elapsedRealtime);
        expectDefaultSettings();
        expectNetworkStatsSummary(new NetworkStats.Builder(elapsedRealtime, 1)
                .addEntry(TEST_IFACE, UID_ALL, 4096L, 8192L).build());
        expectNetworkStatsDetail(buildEmptyStats(elapsedRealtime));

        replay();
        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
        verifyAndReset();

        // verify service recorded history
        assertNetworkTotal(TEMPLATE_WIFI, 4096L, 8192L);
        verifyAndReset();

    }

    public void testHistoryForRebootPersist() throws Exception {
    public void testStatsRebootPersist() throws Exception {
        long elapsedRealtime = 0;
        NetworkState[] state = null;
        NetworkStats stats = null;
        NetworkStats detail = null;

        // assert that no stats file exists
        final File statsFile = new File(mStatsDir, "netstats.bin");
        assertFalse(statsFile.exists());
        assertStatsFilesExist(false);

        // pretend that wifi network comes online; service should ask about full
        // network state, and poll any existing interfaces before updating.
        state = new NetworkState[] { buildWifi() };
        stats = new NetworkStats.Builder(elapsedRealtime, 0).build();
        detail = new NetworkStats.Builder(elapsedRealtime, 0).build();

        expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce();
        expect(mNetManager.getNetworkStatsSummary()).andReturn(stats).atLeastOnce();
        expect(mNetManager.getNetworkStatsDetail()).andReturn(detail).atLeastOnce();
        expectTime(TEST_START + elapsedRealtime);
        expectDefaultSettings();
        expectNetworkState(buildWifiState());
        expectNetworkStatsSummary(buildEmptyStats(elapsedRealtime));

        replay();
        mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
        verifyAndReset();

        // verify service has empty history for wifi
        assertNetworkTotal(TEMPLATE_WIFI, 0L, 0L);
        verifyAndReset();

        // modify some number on wifi, and trigger poll event
        elapsedRealtime += HOUR_IN_MILLIS;
        stats = new NetworkStats.Builder(elapsedRealtime, 1).addEntry(
                TEST_IFACE, UID_ALL, 1024L, 2048L).build();

        expect(mNetManager.getNetworkStatsSummary()).andReturn(stats).atLeastOnce();
        expect(mNetManager.getNetworkStatsDetail()).andReturn(detail).atLeastOnce();
        expectTime(TEST_START + elapsedRealtime);
        expectDefaultSettings();
        expectNetworkStatsSummary(new NetworkStats.Builder(elapsedRealtime, 1)
                .addEntry(TEST_IFACE, UID_ALL, 1024L, 2048L).build());
        // TODO: switch these stats to specific iface
        expectNetworkStatsDetail(new NetworkStats.Builder(elapsedRealtime, 2)
                .addEntry(IFACE_ALL, TEST_UID_1, 512L, 256L)
                .addEntry(IFACE_ALL, TEST_UID_2, 128L, 128L).build());

        replay();
        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
        verifyAndReset();

        // verify service recorded history
        assertNetworkTotal(TEMPLATE_WIFI, 1024L, 2048L);
        assertUidTotal(TEST_UID_1, TEMPLATE_WIFI, 512L, 256L);
        assertUidTotal(TEST_UID_2, TEMPLATE_WIFI, 128L, 128L);
        verifyAndReset();

        // graceful shutdown system, which should trigger persist of stats, and
        // clear any values in memory.
@@ -230,18 +218,84 @@ public class NetworkStatsServiceTest extends AndroidTestCase {

        // talk with zombie service to assert stats have gone; and assert that
        // we persisted them to file.
        expectDefaultSettings();
        replay();
        assertNetworkTotal(TEMPLATE_WIFI, 0L, 0L);
        assertTrue(statsFile.exists());
        verifyAndReset();

        assertStatsFilesExist(true);

        // boot through serviceReady() again
        expectDefaultSettings();
        expectSystemReady();

        replay();
        mService.systemReady();
        verifyAndReset();

        // after systemReady(), we should have historical stats loaded again
        assertNetworkTotal(TEMPLATE_WIFI, 1024L, 2048L);
        assertUidTotal(TEST_UID_1, TEMPLATE_WIFI, 512L, 256L);
        assertUidTotal(TEST_UID_2, TEMPLATE_WIFI, 128L, 128L);
        verifyAndReset();

    }

    public void testStatsBucketResize() throws Exception {
        long elapsedRealtime = 0;
        NetworkStatsHistory history = null;
        long[] total = null;

        assertStatsFilesExist(false);

        // pretend that wifi network comes online; service should ask about full
        // network state, and poll any existing interfaces before updating.
        expectTime(TEST_START + elapsedRealtime);
        expectSettings(0L, HOUR_IN_MILLIS, WEEK_IN_MILLIS);
        expectNetworkState(buildWifiState());
        expectNetworkStatsSummary(buildEmptyStats(elapsedRealtime));

        replay();
        mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
        verifyAndReset();

        // modify some number on wifi, and trigger poll event
        elapsedRealtime += 2 * HOUR_IN_MILLIS;
        expectTime(TEST_START + elapsedRealtime);
        expectSettings(0L, HOUR_IN_MILLIS, WEEK_IN_MILLIS);
        expectNetworkStatsSummary(new NetworkStats.Builder(elapsedRealtime, 1)
                .addEntry(TEST_IFACE, UID_ALL, 512L, 512L).build());
        expectNetworkStatsDetail(buildEmptyStats(elapsedRealtime));

        replay();
        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));

        // verify service recorded history
        history = mService.getHistoryForNetwork(TEMPLATE_WIFI);
        total = history.getTotalData(Long.MIN_VALUE, Long.MAX_VALUE, null);
        assertEquals(512L, total[0]);
        assertEquals(512L, total[1]);
        assertEquals(HOUR_IN_MILLIS, history.bucketDuration);
        assertEquals(2, history.bucketCount);
        verifyAndReset();

        // now change bucket duration setting and trigger another poll with
        // exact same values, which should resize existing buckets.
        expectTime(TEST_START + elapsedRealtime);
        expectSettings(0L, 30 * MINUTE_IN_MILLIS, WEEK_IN_MILLIS);
        expectNetworkStatsSummary(buildEmptyStats(elapsedRealtime));
        expectNetworkStatsDetail(buildEmptyStats(elapsedRealtime));

        replay();
        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));

        // verify identical stats, but spread across 4 buckets now
        history = mService.getHistoryForNetwork(TEMPLATE_WIFI);
        total = history.getTotalData(Long.MIN_VALUE, Long.MAX_VALUE, null);
        assertEquals(512L, total[0]);
        assertEquals(512L, total[1]);
        assertEquals(30 * MINUTE_IN_MILLIS, history.bucketDuration);
        assertEquals(4, history.bucketCount);
        verifyAndReset();

    }

@@ -252,6 +306,13 @@ public class NetworkStatsServiceTest extends AndroidTestCase {
        assertEquals(tx, total[1]);
    }

    private void assertUidTotal(int uid, int template, long rx, long tx) {
        final NetworkStatsHistory history = mService.getHistoryForUid(uid, template);
        final long[] total = history.getTotalData(Long.MIN_VALUE, Long.MAX_VALUE, null);
        assertEquals(rx, total[0]);
        assertEquals(tx, total[1]);
    }

    private void expectSystemReady() throws Exception {
        mAlarmManager.remove(isA(PendingIntent.class));
        expectLastCall().anyTimes();
@@ -261,7 +322,34 @@ public class NetworkStatsServiceTest extends AndroidTestCase {
        expectLastCall().atLeastOnce();
    }

    public void expectTime(long currentTime) throws Exception {
    private void expectNetworkState(NetworkState... state) throws Exception {
        expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce();
    }

    private void expectNetworkStatsSummary(NetworkStats summary) throws Exception {
        expect(mNetManager.getNetworkStatsSummary()).andReturn(summary).atLeastOnce();
    }

    private void expectNetworkStatsDetail(NetworkStats detail) throws Exception {
        expect(mNetManager.getNetworkStatsDetail()).andReturn(detail).atLeastOnce();
    }

    private void expectDefaultSettings() throws Exception {
        expectSettings(0L, HOUR_IN_MILLIS, WEEK_IN_MILLIS);
    }

    private void expectSettings(long persistThreshold, long bucketDuration, long maxHistory)
            throws Exception {
        expect(mSettings.getPollInterval()).andReturn(HOUR_IN_MILLIS).anyTimes();
        expect(mSettings.getPersistThreshold()).andReturn(persistThreshold).anyTimes();
        expect(mSettings.getNetworkBucketDuration()).andReturn(bucketDuration).anyTimes();
        expect(mSettings.getNetworkMaxHistory()).andReturn(maxHistory).anyTimes();
        expect(mSettings.getUidBucketDuration()).andReturn(bucketDuration).anyTimes();
        expect(mSettings.getUidMaxHistory()).andReturn(maxHistory).anyTimes();
        expect(mSettings.getTimeCacheMaxAge()).andReturn(DAY_IN_MILLIS).anyTimes();
    }

    private void expectTime(long currentTime) throws Exception {
        expect(mTime.forceRefresh()).andReturn(false).anyTimes();
        expect(mTime.hasCache()).andReturn(true).anyTimes();
        expect(mTime.currentTimeMillis()).andReturn(currentTime).anyTimes();
@@ -269,12 +357,36 @@ public class NetworkStatsServiceTest extends AndroidTestCase {
        expect(mTime.getCacheCertainty()).andReturn(0L).anyTimes();
    }

    private void assertStatsFilesExist(boolean exist) {
        final File summaryFile = new File(mStatsDir, "netstats.bin");
        final File detailFile = new File(mStatsDir, "netstats_uid.bin");
        if (exist) {
            assertTrue(summaryFile.exists());
            assertTrue(detailFile.exists());
        } else {
            assertFalse(summaryFile.exists());
            assertFalse(detailFile.exists());
        }
    }

    private static NetworkState buildWifiState() {
        final NetworkInfo info = new NetworkInfo(TYPE_WIFI, 0, null, null);
        info.setDetailedState(DetailedState.CONNECTED, null, null);
        final LinkProperties prop = new LinkProperties();
        prop.setInterfaceName(TEST_IFACE);
        return new NetworkState(info, prop, null);
    }

    private static NetworkStats buildEmptyStats(long elapsedRealtime) {
        return new NetworkStats.Builder(elapsedRealtime, 0).build();
    }

    private void replay() {
        EasyMock.replay(mNetManager, mAlarmManager, mTime, mConnManager);
        EasyMock.replay(mNetManager, mAlarmManager, mTime, mSettings, mConnManager);
    }

    private void verifyAndReset() {
        EasyMock.verify(mNetManager, mAlarmManager, mTime, mConnManager);
        EasyMock.reset(mNetManager, mAlarmManager, mTime, mConnManager);
        EasyMock.verify(mNetManager, mAlarmManager, mTime, mSettings, mConnManager);
        EasyMock.reset(mNetManager, mAlarmManager, mTime, mSettings, mConnManager);
    }
}