Persist network stats using AtomicFile.
Implements read/write of network stats using AtomicFile, along with
magic number and versioning. Stores in "/data/system/netstats.bin"
for now. Tests to verify that stats are persisted across a simulated
reboot, and to verify that TEMPLATE_WIFI is working.
Fixed bug where kernel counters rolling backwards would cause negative
stats to be recorded; now we clamp deltas at 0.
Change-Id: I53bce26fc8fd3f4ab1e34ce135d302edfa34db34
diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
new file mode 100644
index 0000000..9846372
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
+import static android.net.ConnectivityManager.TYPE_WIFI;
+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 com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_POLL;
+import static org.easymock.EasyMock.anyLong;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.eq;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.isA;
+
+import android.app.AlarmManager;
+import android.app.IAlarmManager;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.net.IConnectivityManager;
+import android.net.LinkProperties;
+import android.net.NetworkInfo;
+import android.net.NetworkInfo.DetailedState;
+import android.net.NetworkState;
+import android.net.NetworkStats;
+import android.net.NetworkStatsHistory;
+import android.os.INetworkManagementService;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.TrustedTime;
+
+import com.android.server.net.NetworkStatsService;
+
+import org.easymock.EasyMock;
+
+import java.io.File;
+
+/**
+ * Tests for {@link NetworkStatsService}.
+ */
+@LargeTest
+public class NetworkStatsServiceTest extends AndroidTestCase {
+ private static final String TAG = "NetworkStatsServiceTest";
+
+ private static final String TEST_IFACE = "test0";
+ private static final long TEST_START = 1194220800000L;
+
+ private BroadcastInterceptingContext mServiceContext;
+ private File mStatsDir;
+
+ private INetworkManagementService mNetManager;
+ private IAlarmManager mAlarmManager;
+ private TrustedTime mTime;
+ private IConnectivityManager mConnManager;
+
+ private NetworkStatsService mService;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mServiceContext = new BroadcastInterceptingContext(getContext());
+ mStatsDir = getContext().getFilesDir();
+
+ mNetManager = createMock(INetworkManagementService.class);
+ mAlarmManager = createMock(IAlarmManager.class);
+ mTime = createMock(TrustedTime.class);
+ mConnManager = createMock(IConnectivityManager.class);
+
+ mService = new NetworkStatsService(
+ mServiceContext, mNetManager, mAlarmManager, mTime, mStatsDir);
+ mService.bindConnectivityManager(mConnManager);
+
+ expectSystemReady();
+
+ replay();
+ mService.systemReady();
+ verifyAndReset();
+
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ for (File file : mStatsDir.listFiles()) {
+ file.delete();
+ }
+
+ mServiceContext = null;
+ mStatsDir = null;
+
+ mNetManager = null;
+ mAlarmManager = null;
+ mTime = null;
+
+ mService = null;
+
+ 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 {
+ 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);
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
+ verifyAndReset();
+
+ // verify service has empty history for wifi
+ assertNetworkTotal(TEMPLATE_WIFI, 0L, 0L);
+
+ // 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);
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+ verifyAndReset();
+
+ // verify service recorded history
+ assertNetworkTotal(TEMPLATE_WIFI, 1024L, 2048L);
+
+ // 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);
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+ verifyAndReset();
+
+ // verify service recorded history
+ assertNetworkTotal(TEMPLATE_WIFI, 4096L, 8192L);
+ }
+
+ public void testHistoryForRebootPersist() 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());
+
+ // 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);
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
+ verifyAndReset();
+
+ // verify service has empty history for wifi
+ assertNetworkTotal(TEMPLATE_WIFI, 0L, 0L);
+
+ // 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);
+
+ replay();
+ mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+ verifyAndReset();
+
+ // verify service recorded history
+ assertNetworkTotal(TEMPLATE_WIFI, 1024L, 2048L);
+
+ // graceful shutdown system, which should trigger persist of stats, and
+ // clear any values in memory.
+ mServiceContext.sendBroadcast(new Intent(Intent.ACTION_SHUTDOWN));
+
+ // talk with zombie service to assert stats have gone; and assert that
+ // we persisted them to file.
+ assertNetworkTotal(TEMPLATE_WIFI, 0L, 0L);
+ assertTrue(statsFile.exists());
+
+ // boot through serviceReady() again
+ expectSystemReady();
+
+ replay();
+ mService.systemReady();
+ verifyAndReset();
+
+ // after systemReady(), we should have historical stats loaded again
+ assertNetworkTotal(TEMPLATE_WIFI, 1024L, 2048L);
+
+ }
+
+ private void assertNetworkTotal(int template, long rx, long tx) {
+ 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]);
+ }
+
+ private void expectSystemReady() throws Exception {
+ mAlarmManager.remove(isA(PendingIntent.class));
+ expectLastCall().anyTimes();
+
+ mAlarmManager.setInexactRepeating(
+ eq(AlarmManager.ELAPSED_REALTIME), anyLong(), anyLong(), isA(PendingIntent.class));
+ expectLastCall().atLeastOnce();
+ }
+
+ public void expectTime(long currentTime) throws Exception {
+ expect(mTime.forceRefresh()).andReturn(false).anyTimes();
+ expect(mTime.hasCache()).andReturn(true).anyTimes();
+ expect(mTime.currentTimeMillis()).andReturn(currentTime).anyTimes();
+ expect(mTime.getCacheAge()).andReturn(0L).anyTimes();
+ expect(mTime.getCacheCertainty()).andReturn(0L).anyTimes();
+ }
+
+ private void replay() {
+ EasyMock.replay(mNetManager, mAlarmManager, mTime, mConnManager);
+ }
+
+ private void verifyAndReset() {
+ EasyMock.verify(mNetManager, mAlarmManager, mTime, mConnManager);
+ EasyMock.reset(mNetManager, mAlarmManager, mTime, mConnManager);
+ }
+}