From ebbebc73a0ae25912286207139447388f0bdb3cf Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Sun, 5 Jun 2011 17:42:53 -0700 Subject: [PATCH] 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 --- .../src/android/net/NetworkStatsTest.java | 6 +- .../server/NetworkStatsServiceTest.java | 280 ++++++++++++++++++ 2 files changed, 283 insertions(+), 3 deletions(-) create mode 100644 services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java diff --git a/core/tests/coretests/src/android/net/NetworkStatsTest.java b/core/tests/coretests/src/android/net/NetworkStatsTest.java index 23eb9cf8a2..8a3e871edf 100644 --- a/core/tests/coretests/src/android/net/NetworkStatsTest.java +++ b/core/tests/coretests/src/android/net/NetworkStatsTest.java @@ -47,7 +47,7 @@ public class NetworkStatsTest extends TestCase { .addEntry(TEST_IFACE, 100, 1024, 0) .addEntry(TEST_IFACE, 101, 0, 1024).build(); - final NetworkStats result = after.subtract(before, true); + final NetworkStats result = after.subtract(before); // identical data should result in zero delta assertEquals(0, result.rx[0]); @@ -65,7 +65,7 @@ public class NetworkStatsTest extends TestCase { .addEntry(TEST_IFACE, 100, 1025, 2) .addEntry(TEST_IFACE, 101, 3, 1028).build(); - final NetworkStats result = after.subtract(before, true); + final NetworkStats result = after.subtract(before); // expect delta between measurements assertEquals(1, result.rx[0]); @@ -84,7 +84,7 @@ public class NetworkStatsTest extends TestCase { .addEntry(TEST_IFACE, 101, 0, 1024) .addEntry(TEST_IFACE, 102, 1024, 1024).build(); - final NetworkStats result = after.subtract(before, true); + final NetworkStats result = after.subtract(before); // its okay to have new rows assertEquals(0, result.rx[0]); 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 0000000000..98463721b5 --- /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); + } +}