Merge "Persist network stats using AtomicFile."
This commit is contained in:
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -153,6 +153,30 @@ public class NetworkStats implements Parcelable {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @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
|
||||
@@ -160,8 +184,11 @@ public class NetworkStats implements Parcelable {
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user