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
diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
index 9846372..d6e4b8b 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
@@ -18,10 +18,13 @@
 
 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.util.TrustedTime;
 
 import com.android.server.net.NetworkStatsService;
+import com.android.server.net.NetworkStatsService.NetworkStatsSettings;
 
 import org.easymock.EasyMock;
 
@@ -62,12 +66,16 @@
     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 @@
         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 @@
         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 @@
 
         // 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 @@
         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 @@
         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 @@
         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);
     }
 }