summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/java/android/content/Context.java2
-rw-r--r--core/java/android/net/INetworkStatsService.aidl (renamed from core/java/android/os/INetStatService.aidl)28
-rw-r--r--core/java/android/net/NetworkStats.java48
-rw-r--r--core/java/android/net/NetworkStatsHistory.aidl19
-rw-r--r--core/java/android/net/NetworkStatsHistory.java285
-rw-r--r--core/java/android/net/TrafficStats.java3
-rw-r--r--core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java210
-rw-r--r--core/tests/coretests/src/android/net/NetworkStatsTest.java7
8 files changed, 569 insertions, 33 deletions
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index a660bd7..aecec66 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1545,6 +1545,8 @@ public abstract class Context {
public static final String NETWORKMANAGEMENT_SERVICE = "network_management";
/** {@hide} */
+ public static final String NETWORK_STATS_SERVICE = "netstats";
+ /** {@hide} */
public static final String NETWORK_POLICY_SERVICE = "netpolicy";
/**
diff --git a/core/java/android/os/INetStatService.aidl b/core/java/android/net/INetworkStatsService.aidl
index a8f3de0..6d57036 100644
--- a/core/java/android/os/INetStatService.aidl
+++ b/core/java/android/net/INetworkStatsService.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
@@ -14,22 +14,14 @@
* limitations under the License.
*/
-package android.os;
+package android.net;
+
+import android.net.NetworkStatsHistory;
+
+/** {@hide} */
+interface INetworkStatsService {
+
+ NetworkStatsHistory[] getNetworkStatsSummary(int networkType);
+ NetworkStatsHistory getNetworkStatsUid(int uid);
-/**
- * Retrieves packet and byte counts for the phone data interface,
- * and for all interfaces.
- * Used for the data activity icon and the phone status in Settings.
- *
- * {@hide}
- */
-interface INetStatService {
- long getMobileTxPackets();
- long getMobileRxPackets();
- long getMobileTxBytes();
- long getMobileRxBytes();
- long getTotalTxPackets();
- long getTotalRxPackets();
- long getTotalTxBytes();
- long getTotalRxBytes();
}
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index 0f207bc..588bf64 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -22,19 +22,22 @@ import android.os.SystemClock;
import java.io.CharArrayWriter;
import java.io.PrintWriter;
+import java.util.HashSet;
/**
- * Collection of network statistics. Can contain summary details across all
- * interfaces, or details with per-UID granularity. Designed to parcel quickly
- * across process boundaries.
+ * Collection of active network statistics. Can contain summary details across
+ * all interfaces, or details with per-UID granularity. Internally stores data
+ * as a large table, closely matching {@code /proc/} data format. This structure
+ * optimizes for rapid in-memory comparison, but consider using
+ * {@link NetworkStatsHistory} when persisting.
*
* @hide
*/
public class NetworkStats implements Parcelable {
- /** {@link #iface} value when entry is summarized over all interfaces. */
+ /** {@link #iface} value when interface details unavailable. */
public static final String IFACE_ALL = null;
- /** {@link #uid} value when entry is summarized over all UIDs. */
- public static final int UID_ALL = 0;
+ /** {@link #uid} value when UID details unavailable. */
+ public static final int UID_ALL = -1;
// NOTE: data should only be accounted for once in this structure; if data
// is broken out, the summarized version should not be included.
@@ -49,7 +52,7 @@ public class NetworkStats implements Parcelable {
public final long[] rx;
public final long[] tx;
- // TODO: add fg/bg stats and tag granularity
+ // TODO: add fg/bg stats once reported by kernel
private NetworkStats(long elapsedRealtime, String[] iface, int[] uid, long[] rx, long[] tx) {
this.elapsedRealtime = elapsedRealtime;
@@ -120,15 +123,35 @@ public class NetworkStats implements Parcelable {
}
/**
+ * Return list of unique interfaces known by this data structure.
+ */
+ public String[] getKnownIfaces() {
+ final HashSet<String> ifaces = new HashSet<String>();
+ for (String iface : this.iface) {
+ if (iface != IFACE_ALL) {
+ ifaces.add(iface);
+ }
+ }
+ return ifaces.toArray(new String[ifaces.size()]);
+ }
+
+ /**
* 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.
*/
- public NetworkStats subtract(NetworkStats value) {
- // result will have our rows, but no meaningful timestamp
- final int length = length();
- final NetworkStats.Builder result = new NetworkStats.Builder(-1, length);
+ public NetworkStats subtract(NetworkStats value, boolean enforceMonotonic) {
+ final long deltaRealtime = this.elapsedRealtime - value.elapsedRealtime;
+ if (enforceMonotonic && deltaRealtime < 0) {
+ throw new IllegalArgumentException("found non-monotonic realtime");
+ }
+ // result will have our rows, and elapsed time between snapshots
+ final int length = length();
+ final NetworkStats.Builder result = new NetworkStats.Builder(deltaRealtime, length);
for (int i = 0; i < length; i++) {
final String iface = this.iface[i];
final int uid = this.uid[i];
@@ -142,6 +165,9 @@ public class NetworkStats implements Parcelable {
// existing row, subtract remote value
final long rx = this.rx[i] - value.rx[j];
final long tx = this.tx[i] - value.tx[j];
+ if (enforceMonotonic && (rx < 0 || tx < 0)) {
+ throw new IllegalArgumentException("found non-monotonic values");
+ }
result.addEntry(iface, uid, rx, tx);
}
}
diff --git a/core/java/android/net/NetworkStatsHistory.aidl b/core/java/android/net/NetworkStatsHistory.aidl
new file mode 100644
index 0000000..8b9069f
--- /dev/null
+++ b/core/java/android/net/NetworkStatsHistory.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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 android.net;
+
+parcelable NetworkStatsHistory;
diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java
new file mode 100644
index 0000000..b16101f
--- /dev/null
+++ b/core/java/android/net/NetworkStatsHistory.java
@@ -0,0 +1,285 @@
+/*
+ * 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 android.net;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.io.CharArrayWriter;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Arrays;
+
+/**
+ * Collection of historical network statistics, recorded into equally-sized
+ * "buckets" in time. Internally it stores data in {@code long} series for more
+ * efficient persistence.
+ * <p>
+ * Each bucket is defined by a {@link #bucketStart} timestamp, and lasts for
+ * {@link #bucketDuration}. Internally assumes that {@link #bucketStart} is
+ * sorted at all times.
+ *
+ * @hide
+ */
+public class NetworkStatsHistory implements Parcelable {
+ private static final int VERSION = 1;
+
+ /** {@link #uid} value when UID details unavailable. */
+ public static final int UID_ALL = -1;
+
+ // TODO: teach about zigzag encoding to use less disk space
+ // TODO: teach how to convert between bucket sizes
+
+ public final int networkType;
+ public final String identity;
+ public final int uid;
+ public final long bucketDuration;
+
+ int bucketCount;
+ long[] bucketStart;
+ long[] rx;
+ long[] tx;
+
+ public NetworkStatsHistory(int networkType, String identity, int uid, long bucketDuration) {
+ this.networkType = networkType;
+ this.identity = identity;
+ this.uid = uid;
+ this.bucketDuration = bucketDuration;
+ bucketStart = new long[0];
+ rx = new long[0];
+ tx = new long[0];
+ bucketCount = bucketStart.length;
+ }
+
+ public NetworkStatsHistory(Parcel in) {
+ networkType = in.readInt();
+ identity = in.readString();
+ uid = in.readInt();
+ bucketDuration = in.readLong();
+ bucketStart = readLongArray(in);
+ rx = in.createLongArray();
+ tx = in.createLongArray();
+ bucketCount = bucketStart.length;
+ }
+
+ /** {@inheritDoc} */
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(networkType);
+ out.writeString(identity);
+ out.writeInt(uid);
+ out.writeLong(bucketDuration);
+ writeLongArray(out, bucketStart, bucketCount);
+ writeLongArray(out, rx, bucketCount);
+ writeLongArray(out, tx, bucketCount);
+ }
+
+ public NetworkStatsHistory(DataInputStream in) throws IOException {
+ final int version = in.readInt();
+ networkType = in.readInt();
+ identity = in.readUTF();
+ uid = in.readInt();
+ bucketDuration = in.readLong();
+ bucketStart = readLongArray(in);
+ rx = readLongArray(in);
+ tx = readLongArray(in);
+ bucketCount = bucketStart.length;
+ }
+
+ public void writeToStream(DataOutputStream out) throws IOException {
+ out.writeInt(VERSION);
+ out.writeInt(networkType);
+ out.writeUTF(identity);
+ out.writeInt(uid);
+ out.writeLong(bucketDuration);
+ writeLongArray(out, bucketStart, bucketCount);
+ writeLongArray(out, rx, bucketCount);
+ writeLongArray(out, tx, bucketCount);
+ }
+
+ /** {@inheritDoc} */
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Record that data traffic occurred in the given time range. Will
+ * distribute across internal buckets, creating new buckets as needed.
+ */
+ public void recordData(long start, long end, long rx, long tx) {
+ // create any buckets needed by this range
+ ensureBuckets(start, end);
+
+ // distribute data usage into buckets
+ final long duration = end - start;
+ for (int i = bucketCount - 1; i >= 0; i--) {
+ final long curStart = bucketStart[i];
+ final long curEnd = curStart + bucketDuration;
+
+ // bucket is older than record; we're finished
+ if (curEnd < start) break;
+ // bucket is newer than record; keep looking
+ if (curStart > end) continue;
+
+ final long overlap = Math.min(curEnd, end) - Math.max(curStart, start);
+ if (overlap > 0) {
+ this.rx[i] += rx * overlap / duration;
+ this.tx[i] += tx * overlap / duration;
+ }
+ }
+ }
+
+ /**
+ * Ensure that buckets exist for given time range, creating as needed.
+ */
+ private void ensureBuckets(long start, long end) {
+ // normalize incoming range to bucket boundaries
+ start -= start % bucketDuration;
+ end += (bucketDuration - (end % bucketDuration)) % bucketDuration;
+
+ for (long now = start; now < end; now += bucketDuration) {
+ // try finding existing bucket
+ final int index = Arrays.binarySearch(bucketStart, 0, bucketCount, now);
+ if (index < 0) {
+ // bucket missing, create and insert
+ insertBucket(~index, now);
+ }
+ }
+ }
+
+ /**
+ * Insert new bucket at requested index and starting time.
+ */
+ private void insertBucket(int index, long start) {
+ // create more buckets when needed
+ if (bucketCount + 1 > bucketStart.length) {
+ final int newLength = bucketStart.length + 10;
+ bucketStart = Arrays.copyOf(bucketStart, newLength);
+ rx = Arrays.copyOf(rx, newLength);
+ tx = Arrays.copyOf(tx, newLength);
+ }
+
+ // create gap when inserting bucket in middle
+ if (index < bucketCount) {
+ final int dstPos = index + 1;
+ final int length = bucketCount - index;
+
+ System.arraycopy(bucketStart, index, bucketStart, dstPos, length);
+ System.arraycopy(rx, index, rx, dstPos, length);
+ System.arraycopy(tx, index, tx, dstPos, length);
+ }
+
+ bucketStart[index] = start;
+ rx[index] = 0;
+ tx[index] = 0;
+ bucketCount++;
+ }
+
+ /**
+ * Remove buckets older than requested cutoff.
+ */
+ public void removeBucketsBefore(long cutoff) {
+ int i;
+ for (i = 0; i < bucketCount; i++) {
+ final long curStart = bucketStart[i];
+ final long curEnd = curStart + bucketDuration;
+
+ // cutoff happens before or during this bucket; everything before
+ // this bucket should be removed.
+ if (curEnd > cutoff) break;
+ }
+
+ if (i > 0) {
+ final int length = bucketStart.length;
+ bucketStart = Arrays.copyOfRange(bucketStart, i, length);
+ rx = Arrays.copyOfRange(rx, i, length);
+ tx = Arrays.copyOfRange(tx, i, length);
+ bucketCount -= i;
+ }
+ }
+
+ public void dump(String prefix, PrintWriter pw) {
+ // TODO: consider stripping identity when dumping
+ pw.print(prefix);
+ pw.print("NetworkStatsHistory: networkType="); pw.print(networkType);
+ pw.print(" identity="); pw.print(identity);
+ pw.print(" uid="); pw.println(uid);
+ for (int i = 0; i < bucketCount; i++) {
+ pw.print(prefix);
+ pw.print(" timestamp="); pw.print(bucketStart[i]);
+ pw.print(" rx="); pw.print(rx[i]);
+ pw.print(" tx="); pw.println(tx[i]);
+ }
+ }
+
+ @Override
+ public String toString() {
+ final CharArrayWriter writer = new CharArrayWriter();
+ dump("", new PrintWriter(writer));
+ return writer.toString();
+ }
+
+ public static final Creator<NetworkStatsHistory> CREATOR = new Creator<NetworkStatsHistory>() {
+ public NetworkStatsHistory createFromParcel(Parcel in) {
+ return new NetworkStatsHistory(in);
+ }
+
+ public NetworkStatsHistory[] newArray(int size) {
+ return new NetworkStatsHistory[size];
+ }
+ };
+
+ private static long[] readLongArray(DataInputStream in) throws IOException {
+ final int size = in.readInt();
+ final long[] values = new long[size];
+ for (int i = 0; i < values.length; i++) {
+ values[i] = in.readLong();
+ }
+ return values;
+ }
+
+ private static void writeLongArray(DataOutputStream out, long[] values, int size) throws IOException {
+ if (size > values.length) {
+ throw new IllegalArgumentException("size larger than length");
+ }
+ out.writeInt(size);
+ for (int i = 0; i < size; i++) {
+ out.writeLong(values[i]);
+ }
+ }
+
+ private static long[] readLongArray(Parcel in) {
+ final int size = in.readInt();
+ final long[] values = new long[size];
+ for (int i = 0; i < values.length; i++) {
+ values[i] = in.readLong();
+ }
+ return values;
+ }
+
+ private static void writeLongArray(Parcel out, long[] values, int size) {
+ if (size > values.length) {
+ throw new IllegalArgumentException("size larger than length");
+ }
+ out.writeInt(size);
+ for (int i = 0; i < size; i++) {
+ out.writeLong(values[i]);
+ }
+ }
+
+}
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index c0ff734..8ab64fa 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -141,7 +141,8 @@ public class TrafficStats {
// subtract starting values and return delta
final NetworkStats profilingStop = getNetworkStatsForUid(context);
- final NetworkStats profilingDelta = profilingStop.subtract(sActiveProfilingStart);
+ final NetworkStats profilingDelta = profilingStop.subtract(
+ sActiveProfilingStart, false);
sActiveProfilingStart = null;
return profilingDelta;
}
diff --git a/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java b/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java
new file mode 100644
index 0000000..eb63c0d
--- /dev/null
+++ b/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java
@@ -0,0 +1,210 @@
+/*
+ * 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 android.net;
+
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.NetworkStatsHistory.UID_ALL;
+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.SECOND_IN_MILLIS;
+import static android.text.format.DateUtils.WEEK_IN_MILLIS;
+import static android.text.format.DateUtils.YEAR_IN_MILLIS;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.Suppress;
+import android.util.Log;
+
+import junit.framework.TestCase;
+
+import java.util.Random;
+
+@SmallTest
+public class NetworkStatsHistoryTest extends TestCase {
+ private static final String TAG = "NetworkStatsHistoryTest";
+
+ private static final long TEST_START = 1194220800000L;
+
+ private NetworkStatsHistory stats;
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ if (stats != null) {
+ assertConsistent(stats);
+ }
+ }
+
+ public void testRecordSingleBucket() throws Exception {
+ final long BUCKET_SIZE = HOUR_IN_MILLIS;
+ stats = buildStats(BUCKET_SIZE);
+
+ // record data into narrow window to get single bucket
+ stats.recordData(TEST_START, TEST_START + SECOND_IN_MILLIS, 1024L, 2048L);
+
+ assertEquals(1, stats.bucketCount);
+ assertBucket(stats, 0, 1024L, 2048L);
+ }
+
+ public void testRecordEqualBuckets() throws Exception {
+ final long bucketDuration = HOUR_IN_MILLIS;
+ stats = buildStats(bucketDuration);
+
+ // split equally across two buckets
+ final long recordStart = TEST_START + (bucketDuration / 2);
+ stats.recordData(recordStart, recordStart + bucketDuration, 1024L, 128L);
+
+ assertEquals(2, stats.bucketCount);
+ assertBucket(stats, 0, 512L, 64L);
+ assertBucket(stats, 1, 512L, 64L);
+ }
+
+ public void testRecordTouchingBuckets() throws Exception {
+ final long BUCKET_SIZE = 15 * MINUTE_IN_MILLIS;
+ stats = buildStats(BUCKET_SIZE);
+
+ // split almost completely into middle bucket, but with a few minutes
+ // overlap into neighboring buckets. total record is 20 minutes.
+ final long recordStart = (TEST_START + BUCKET_SIZE) - MINUTE_IN_MILLIS;
+ final long recordEnd = (TEST_START + (BUCKET_SIZE * 2)) + (MINUTE_IN_MILLIS * 4);
+ stats.recordData(recordStart, recordEnd, 1000L, 5000L);
+
+ assertEquals(3, stats.bucketCount);
+ // first bucket should have (1/20 of value)
+ assertBucket(stats, 0, 50L, 250L);
+ // second bucket should have (15/20 of value)
+ assertBucket(stats, 1, 750L, 3750L);
+ // final bucket should have (4/20 of value)
+ assertBucket(stats, 2, 200L, 1000L);
+ }
+
+ public void testRecordGapBuckets() throws Exception {
+ final long BUCKET_SIZE = HOUR_IN_MILLIS;
+ stats = buildStats(BUCKET_SIZE);
+
+ // record some data today and next week with large gap
+ final long firstStart = TEST_START;
+ final long lastStart = TEST_START + WEEK_IN_MILLIS;
+ stats.recordData(firstStart, firstStart + SECOND_IN_MILLIS, 128L, 256L);
+ stats.recordData(lastStart, lastStart + SECOND_IN_MILLIS, 64L, 512L);
+
+ // we should have two buckets, far apart from each other
+ assertEquals(2, stats.bucketCount);
+ assertBucket(stats, 0, 128L, 256L);
+ assertBucket(stats, 1, 64L, 512L);
+
+ // now record something in middle, spread across two buckets
+ final long middleStart = TEST_START + DAY_IN_MILLIS;
+ final long middleEnd = middleStart + (HOUR_IN_MILLIS * 2);
+ stats.recordData(middleStart, middleEnd, 2048L, 2048L);
+
+ // now should have four buckets, with new record in middle two buckets
+ assertEquals(4, stats.bucketCount);
+ assertBucket(stats, 0, 128L, 256L);
+ assertBucket(stats, 1, 1024L, 1024L);
+ assertBucket(stats, 2, 1024L, 1024L);
+ assertBucket(stats, 3, 64L, 512L);
+ }
+
+ public void testRecordOverlapBuckets() throws Exception {
+ final long BUCKET_SIZE = HOUR_IN_MILLIS;
+ stats = buildStats(BUCKET_SIZE);
+
+ // record some data in one bucket, and another overlapping buckets
+ stats.recordData(TEST_START, TEST_START + SECOND_IN_MILLIS, 256L, 256L);
+ final long midStart = TEST_START + (HOUR_IN_MILLIS / 2);
+ stats.recordData(midStart, midStart + HOUR_IN_MILLIS, 1024L, 1024L);
+
+ // should have two buckets, with some data mixed together
+ assertEquals(2, stats.bucketCount);
+ assertBucket(stats, 0, 768L, 768L);
+ assertBucket(stats, 1, 512L, 512L);
+ }
+
+ public void testRemove() throws Exception {
+ final long BUCKET_SIZE = HOUR_IN_MILLIS;
+ stats = buildStats(BUCKET_SIZE);
+
+ // record some data across 24 buckets
+ stats.recordData(TEST_START, TEST_START + DAY_IN_MILLIS, 24L, 24L);
+ assertEquals(24, stats.bucketCount);
+
+ // try removing far before buckets; should be no change
+ stats.removeBucketsBefore(TEST_START - YEAR_IN_MILLIS);
+ assertEquals(24, stats.bucketCount);
+
+ // try removing just moments into first bucket; should be no change
+ // since that bucket contains data beyond the cutoff
+ stats.removeBucketsBefore(TEST_START + SECOND_IN_MILLIS);
+ assertEquals(24, stats.bucketCount);
+
+ // try removing single bucket
+ stats.removeBucketsBefore(TEST_START + HOUR_IN_MILLIS);
+ assertEquals(23, stats.bucketCount);
+
+ // try removing multiple buckets
+ stats.removeBucketsBefore(TEST_START + (4 * HOUR_IN_MILLIS));
+ assertEquals(20, stats.bucketCount);
+
+ // try removing all buckets
+ stats.removeBucketsBefore(TEST_START + YEAR_IN_MILLIS);
+ assertEquals(0, stats.bucketCount);
+ }
+
+ @Suppress
+ public void testFuzzing() throws Exception {
+ try {
+ // fuzzing with random events, looking for crashes
+ final Random r = new Random();
+ for (int i = 0; i < 500; i++) {
+ stats = buildStats(r.nextLong());
+ for (int j = 0; j < 10000; j++) {
+ if (r.nextBoolean()) {
+ // add range
+ final long start = r.nextLong();
+ final long end = start + r.nextInt();
+ stats.recordData(start, end, r.nextLong(), r.nextLong());
+ } else {
+ // trim something
+ stats.removeBucketsBefore(r.nextLong());
+ }
+ }
+ assertConsistent(stats);
+ }
+ } catch (Throwable e) {
+ Log.e(TAG, String.valueOf(stats));
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static NetworkStatsHistory buildStats(long bucketSize) {
+ return new NetworkStatsHistory(TYPE_MOBILE, null, UID_ALL, bucketSize);
+ }
+
+ private static void assertConsistent(NetworkStatsHistory stats) {
+ // verify timestamps are monotonic
+ for (int i = 1; i < stats.bucketCount; i++) {
+ assertTrue(stats.bucketStart[i - 1] < stats.bucketStart[i]);
+ }
+ }
+
+ private static void assertBucket(NetworkStatsHistory stats, int index, long rx, long tx) {
+ assertEquals("unexpected rx", rx, stats.rx[index]);
+ assertEquals("unexpected tx", tx, stats.tx[index]);
+ }
+
+}
diff --git a/core/tests/coretests/src/android/net/NetworkStatsTest.java b/core/tests/coretests/src/android/net/NetworkStatsTest.java
index 45719c2..23eb9cf 100644
--- a/core/tests/coretests/src/android/net/NetworkStatsTest.java
+++ b/core/tests/coretests/src/android/net/NetworkStatsTest.java
@@ -47,8 +47,9 @@ public class NetworkStatsTest extends TestCase {
.addEntry(TEST_IFACE, 100, 1024, 0)
.addEntry(TEST_IFACE, 101, 0, 1024).build();
- final NetworkStats result = after.subtract(before);
+ final NetworkStats result = after.subtract(before, true);
+ // identical data should result in zero delta
assertEquals(0, result.rx[0]);
assertEquals(0, result.tx[0]);
assertEquals(0, result.rx[1]);
@@ -64,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);
+ final NetworkStats result = after.subtract(before, true);
// expect delta between measurements
assertEquals(1, result.rx[0]);
@@ -83,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);
+ final NetworkStats result = after.subtract(before, true);
// its okay to have new rows
assertEquals(0, result.rx[0]);