diff options
Diffstat (limited to 'core')
-rw-r--r-- | core/java/android/content/Context.java | 2 | ||||
-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.java | 48 | ||||
-rw-r--r-- | core/java/android/net/NetworkStatsHistory.aidl | 19 | ||||
-rw-r--r-- | core/java/android/net/NetworkStatsHistory.java | 285 | ||||
-rw-r--r-- | core/java/android/net/TrafficStats.java | 3 | ||||
-rw-r--r-- | core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java | 210 | ||||
-rw-r--r-- | core/tests/coretests/src/android/net/NetworkStatsTest.java | 7 |
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]); |