diff options
| author | 2011-06-09 10:22:27 -0700 | |
|---|---|---|
| committer | 2011-06-09 10:22:27 -0700 | |
| commit | 760145e3af6c51fcd7684b4a456f5eb8a8a330ce (patch) | |
| tree | ffcb55b9911ec59cd3a96de5e9165cab35b5d9f3 | |
| parent | 430ad4733472592a7bb3ff3f88db7385a7728edb (diff) | |
| parent | 3f3913550c10792edb8aecf66cc83c3db5c8b311 (diff) | |
Merge "Persist network stats using AtomicFile."
8 files changed, 462 insertions, 25 deletions
diff --git a/core/java/android/net/INetworkStatsService.aidl b/core/java/android/net/INetworkStatsService.aidl index d38d16c4e759..d05c9d3969b9 100644 --- a/core/java/android/net/INetworkStatsService.aidl +++ b/core/java/android/net/INetworkStatsService.aidl @@ -28,6 +28,6 @@ interface INetworkStatsService { NetworkStatsHistory getHistoryForUid(int uid, int networkTemplate); /** Return usage summary per UID for traffic that matches template. */ - NetworkStats getSummaryPerUid(long start, long end, int networkTemplate); + NetworkStats getSummaryForAllUid(long start, long end, int networkTemplate); } diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java index ee415fa6bc65..6354e9a65e83 100644 --- a/core/java/android/net/NetworkStats.java +++ b/core/java/android/net/NetworkStats.java @@ -158,10 +158,37 @@ public class NetworkStats implements Parcelable { * between two snapshots in time. Assumes that statistics rows collect over * time, and that none of them have disappeared. * + * @throws IllegalArgumentException when given {@link NetworkStats} is + * non-monotonic. + */ + public NetworkStats subtract(NetworkStats value) { + return subtract(value, true, false); + } + + /** + * Subtract the given {@link NetworkStats}, effectively leaving the delta + * between two snapshots in time. Assumes that statistics rows collect over + * time, and that none of them have disappeared. + * <p> + * Instead of throwing when counters are non-monotonic, this variant clamps + * results to never be negative. + */ + public NetworkStats subtractClamped(NetworkStats value) { + return subtract(value, false, true); + } + + /** + * Subtract the given {@link NetworkStats}, effectively leaving the delta + * between two snapshots in time. Assumes that statistics rows collect over + * time, and that none of them have disappeared. + * * @param enforceMonotonic Validate that incoming value is strictly * monotonic compared to this object. + * @param clampNegative Instead of throwing like {@code enforceMonotonic}, + * clamp resulting counters at 0 to prevent negative values. */ - public NetworkStats subtract(NetworkStats value, boolean enforceMonotonic) { + private NetworkStats subtract( + NetworkStats value, boolean enforceMonotonic, boolean clampNegative) { final long deltaRealtime = this.elapsedRealtime - value.elapsedRealtime; if (enforceMonotonic && deltaRealtime < 0) { throw new IllegalArgumentException("found non-monotonic realtime"); @@ -181,11 +208,15 @@ public class NetworkStats implements Parcelable { result.addEntry(iface, uid, this.rx[i], this.tx[i]); } else { // existing row, subtract remote value - final long rx = this.rx[i] - value.rx[j]; - final long tx = this.tx[i] - value.tx[j]; + long rx = this.rx[i] - value.rx[j]; + long tx = this.tx[i] - value.tx[j]; if (enforceMonotonic && (rx < 0 || tx < 0)) { throw new IllegalArgumentException("found non-monotonic values"); } + if (clampNegative) { + rx = Math.max(0, rx); + tx = Math.max(0, tx); + } result.addEntry(iface, uid, rx, tx); } } diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java index 5edbf5830dfe..a697e96989d1 100644 --- a/core/java/android/net/NetworkStatsHistory.java +++ b/core/java/android/net/NetworkStatsHistory.java @@ -218,7 +218,7 @@ 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) { + public long[] getTotalData(long start, long end, long[] outTotal) { long rx = 0; long tx = 0; @@ -238,8 +238,12 @@ public class NetworkStatsHistory implements Parcelable { } } + if (outTotal == null || outTotal.length != 2) { + outTotal = new long[2]; + } outTotal[0] = rx; outTotal[1] = tx; + return outTotal; } /** diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java index a0738c1da2d6..8a688d55f70c 100644 --- a/core/java/android/net/TrafficStats.java +++ b/core/java/android/net/TrafficStats.java @@ -177,8 +177,8 @@ public class TrafficStats { // subtract starting values and return delta final NetworkStats profilingStop = getNetworkStatsForUid(context); - final NetworkStats profilingDelta = profilingStop.subtract( - sActiveProfilingStart, false); + final NetworkStats profilingDelta = profilingStop.subtractClamped( + sActiveProfilingStart); sActiveProfilingStart = null; return profilingDelta; } diff --git a/core/tests/coretests/src/android/net/NetworkStatsTest.java b/core/tests/coretests/src/android/net/NetworkStatsTest.java index 23eb9cf8a215..8a3e871edfa6 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/java/com/android/server/net/NetworkIdentity.java b/services/java/com/android/server/net/NetworkIdentity.java index 79feb95f3463..f7a7c49730cf 100644 --- a/services/java/com/android/server/net/NetworkIdentity.java +++ b/services/java/com/android/server/net/NetworkIdentity.java @@ -66,7 +66,7 @@ public class NetworkIdentity { case VERSION_CURRENT: { type = in.readInt(); subType = in.readInt(); - subscriberId = in.readUTF(); + subscriberId = readOptionalString(in); break; } default: { @@ -79,7 +79,7 @@ public class NetworkIdentity { out.writeInt(VERSION_CURRENT); out.writeInt(type); out.writeInt(subType); - out.writeUTF(subscriberId); + writeOptionalString(out, subscriberId); } @Override @@ -205,4 +205,21 @@ public class NetworkIdentity { return new NetworkIdentity(type, subType, subscriberId); } + private static void writeOptionalString(DataOutputStream out, String value) throws IOException { + if (value != null) { + out.writeByte(1); + out.writeUTF(value); + } else { + out.writeByte(0); + } + } + + private static String readOptionalString(DataInputStream in) throws IOException { + if (in.readByte() != 0) { + return in.readUTF(); + } else { + return null; + } + } + } diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java index 3892de81319b..8db283947137 100644 --- a/services/java/com/android/server/net/NetworkStatsService.java +++ b/services/java/com/android/server/net/NetworkStatsService.java @@ -48,6 +48,7 @@ import android.net.NetworkInfo; import android.net.NetworkState; import android.net.NetworkStats; import android.net.NetworkStatsHistory; +import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; import android.os.INetworkManagementService; @@ -60,15 +61,26 @@ import android.util.Slog; import android.util.SparseArray; import android.util.TrustedTime; +import com.android.internal.os.AtomicFile; import com.google.android.collect.Lists; import com.google.android.collect.Maps; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; import java.io.PrintWriter; +import java.net.ProtocolException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import libcore.io.IoUtils; + /** * Collect and persist detailed network statistics, and provide this data to * other system services. @@ -77,6 +89,10 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private static final String TAG = "NetworkStatsService"; private static final boolean LOGD = true; + /** File header magic number: "ANET" */ + private static final int FILE_MAGIC = 0x414E4554; + private static final int VERSION_CURRENT = 1; + private final Context mContext; private final INetworkManagementService mNetworkManager; private final IAlarmManager mAlarmManager; @@ -84,7 +100,8 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private IConnectivityManager mConnManager; - private static final String ACTION_NETWORK_STATS_POLL = + // @VisibleForTesting + public static final String ACTION_NETWORK_STATS_POLL = "com.android.server.action.NETWORK_STATS_POLL"; private PendingIntent mPollIntent; @@ -98,14 +115,15 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private LongSecureSetting mPollInterval = new LongSecureSetting( NETSTATS_POLL_INTERVAL, 15 * MINUTE_IN_MILLIS); private LongSecureSetting mPersistThreshold = new LongSecureSetting( - NETSTATS_PERSIST_THRESHOLD, 64 * KB_IN_BYTES); + NETSTATS_PERSIST_THRESHOLD, 16 * KB_IN_BYTES); + // TODO: adjust these timings for production builds private LongSecureSetting mSummaryBucketDuration = new LongSecureSetting( - NETSTATS_SUMMARY_BUCKET_DURATION, 6 * HOUR_IN_MILLIS); + NETSTATS_SUMMARY_BUCKET_DURATION, 1 * HOUR_IN_MILLIS); private LongSecureSetting mSummaryMaxHistory = new LongSecureSetting( NETSTATS_SUMMARY_MAX_HISTORY, 90 * DAY_IN_MILLIS); private LongSecureSetting mDetailBucketDuration = new LongSecureSetting( - NETSTATS_DETAIL_BUCKET_DURATION, 6 * HOUR_IN_MILLIS); + NETSTATS_DETAIL_BUCKET_DURATION, 2 * HOUR_IN_MILLIS); private LongSecureSetting mDetailMaxHistory = new LongSecureSetting( NETSTATS_DETAIL_MAX_HISTORY, 90 * DAY_IN_MILLIS); @@ -129,6 +147,8 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private final HandlerThread mHandlerThread; private final Handler mHandler; + private final AtomicFile mSummaryFile; + // TODO: collect detailed uid stats, storing tag-granularity data until next // dropbox, and uid summary for a specific bucket count. @@ -137,11 +157,15 @@ public class NetworkStatsService extends INetworkStatsService.Stub { public NetworkStatsService( Context context, INetworkManagementService networkManager, IAlarmManager alarmManager) { // TODO: move to using cached NtpTrustedTime - this(context, networkManager, alarmManager, new NtpTrustedTime()); + this(context, networkManager, alarmManager, new NtpTrustedTime(), getSystemDir()); + } + + private static File getSystemDir() { + return new File(Environment.getDataDirectory(), "system"); } public NetworkStatsService(Context context, INetworkManagementService networkManager, - IAlarmManager alarmManager, TrustedTime time) { + IAlarmManager alarmManager, TrustedTime time, File systemDir) { mContext = checkNotNull(context, "missing Context"); mNetworkManager = checkNotNull(networkManager, "missing INetworkManagementService"); mAlarmManager = checkNotNull(alarmManager, "missing IAlarmManager"); @@ -150,11 +174,15 @@ public class NetworkStatsService extends INetworkStatsService.Stub { mHandlerThread = new HandlerThread(TAG); mHandlerThread.start(); mHandler = new Handler(mHandlerThread.getLooper()); + + mSummaryFile = new AtomicFile(new File(systemDir, "netstats.bin")); } public void systemReady() { - // read historical stats from disk - readStatsLocked(); + synchronized (mStatsLock) { + // read historical stats from disk + readStatsLocked(); + } // watch other system services that claim interfaces final IntentFilter ifaceFilter = new IntentFilter(); @@ -180,6 +208,16 @@ public class NetworkStatsService extends INetworkStatsService.Stub { mConnManager = checkNotNull(connManager, "missing IConnectivityManager"); } + private void shutdownLocked() { + mContext.unregisterReceiver(mIfaceReceiver); + mContext.unregisterReceiver(mPollReceiver); + mContext.unregisterReceiver(mShutdownReceiver); + + writeStatsLocked(); + mSummaryStats.clear(); + mDetailStats.clear(); + } + /** * Clear any existing {@link #ACTION_NETWORK_STATS_POLL} alarms, and * reschedule based on current {@link #mPollInterval} value. @@ -227,7 +265,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } @Override - public NetworkStats getSummaryPerUid(long start, long end, int networkTemplate) { + public NetworkStats getSummaryForAllUid(long start, long end, int networkTemplate) { // TODO: create relaxed permission for reading stats mContext.enforceCallingOrSelfPermission(UPDATE_DEVICE_STATS, TAG); @@ -281,7 +319,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { public void onReceive(Context context, Intent intent) { // verified SHUTDOWN permission above. synchronized (mStatsLock) { - writeStatsLocked(); + shutdownLocked(); } } }; @@ -440,13 +478,74 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private void readStatsLocked() { if (LOGD) Slog.v(TAG, "readStatsLocked()"); - // TODO: read historical stats from disk using AtomicFile + + // clear any existing stats and read from disk + mSummaryStats.clear(); + + FileInputStream fis = null; + try { + fis = mSummaryFile.openRead(); + final DataInputStream in = new DataInputStream(fis); + + // verify file magic header intact + final int magic = in.readInt(); + if (magic != FILE_MAGIC) { + throw new ProtocolException("unexpected magic: " + magic); + } + + final int version = in.readInt(); + switch (version) { + case VERSION_CURRENT: { + // file format is pairs of interfaces and stats: + // summary := size *(InterfaceIdentity NetworkStatsHistory) + + final int size = in.readInt(); + for (int i = 0; i < size; i++) { + final InterfaceIdentity ident = new InterfaceIdentity(in); + final NetworkStatsHistory history = new NetworkStatsHistory(in); + mSummaryStats.put(ident, history); + } + break; + } + default: { + throw new ProtocolException("unexpected version: " + version); + } + } + } catch (FileNotFoundException e) { + // missing stats is okay, probably first boot + } catch (IOException e) { + Slog.e(TAG, "problem reading network stats", e); + } finally { + IoUtils.closeQuietly(fis); + } } private void writeStatsLocked() { if (LOGD) Slog.v(TAG, "writeStatsLocked()"); - // TODO: persist historical stats to disk using AtomicFile + // TODO: consider duplicating stats and releasing lock while writing + + FileOutputStream fos = null; + try { + fos = mSummaryFile.startWrite(); + final DataOutputStream out = new DataOutputStream(fos); + + out.writeInt(FILE_MAGIC); + out.writeInt(VERSION_CURRENT); + + out.writeInt(mSummaryStats.size()); + for (InterfaceIdentity ident : mSummaryStats.keySet()) { + final NetworkStatsHistory history = mSummaryStats.get(ident); + ident.writeToStream(out); + history.writeToStream(out); + } + + mSummaryFile.finishWrite(fos); + } catch (IOException e) { + if (fos != null) { + mSummaryFile.failWrite(fos); + } + } } @Override @@ -466,6 +565,12 @@ public class NetworkStatsService extends INetworkStatsService.Stub { return; } + if (argSet.contains("poll")) { + performPollLocked(); + pw.println("Forced poll"); + return; + } + pw.println("Active interfaces:"); for (String iface : mActiveIface.keySet()) { final InterfaceIdentity ident = mActiveIface.get(iface); @@ -545,7 +650,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { */ private static NetworkStats computeStatsDelta(NetworkStats before, NetworkStats current) { if (before != null) { - return current.subtract(before, false); + return current.subtractClamped(before); } else { return current; } 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 000000000000..98463721b501 --- /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); + } +} |