Correct proc file reader, optimizations.
Moved away from BufferedReader, which only reads the first 8KB of
some proc files because it aggresively fills its buffer. Optimized
proc parsing, now double the speed. Tests to cover.
Log when NetworkStats counters roll backwards when subtracting, and
optimizations around findIndex(). When system removes UID, also
remove from last stats snapshot to avoid xt counters from rolling
backwards.
Bug: 5472949, 5458380
Change-Id: I07c08fe5233156fac2b84450f6291868bf9bfaf2
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index 69ac1e7..5c6ef1a 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -16,10 +16,11 @@
package android.net;
+import static com.android.internal.util.Preconditions.checkNotNull;
+
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
-import android.util.Log;
import android.util.SparseBooleanArray;
import com.android.internal.util.Objects;
@@ -54,6 +55,8 @@
/** {@link #tag} value for total data across all tags. */
public static final int TAG_NONE = 0;
+ // TODO: move fields to "mVariable" notation
+
/**
* {@link SystemClock#elapsedRealtime()} timestamp when this data was
* generated.
@@ -295,8 +298,33 @@
*/
public int findIndex(String iface, int uid, int set, int tag) {
for (int i = 0; i < size; i++) {
- if (Objects.equal(iface, this.iface[i]) && uid == this.uid[i] && set == this.set[i]
- && tag == this.tag[i]) {
+ if (uid == this.uid[i] && set == this.set[i] && tag == this.tag[i]
+ && Objects.equal(iface, this.iface[i])) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Find first stats index that matches the requested parameters, starting
+ * search around the hinted index as an optimization.
+ */
+ // @VisibleForTesting
+ public int findIndexHinted(String iface, int uid, int set, int tag, int hintIndex) {
+ for (int offset = 0; offset < size; offset++) {
+ final int halfOffset = offset / 2;
+
+ // search outwards from hint index, alternating forward and backward
+ final int i;
+ if (offset % 2 == 0) {
+ i = (hintIndex + halfOffset) % size;
+ } else {
+ i = (size + hintIndex - halfOffset - 1) % size;
+ }
+
+ if (uid == this.uid[i] && set == this.set[i] && tag == this.tag[i]
+ && Objects.equal(iface, this.iface[i])) {
return i;
}
}
@@ -423,40 +451,10 @@
* 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
- * 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.
- */
- private NetworkStats subtract(
- NetworkStats value, boolean enforceMonotonic, boolean clampNegative) {
+ public NetworkStats subtract(NetworkStats value) throws NonMonotonicException {
final long deltaRealtime = this.elapsedRealtime - value.elapsedRealtime;
- if (enforceMonotonic && deltaRealtime < 0) {
+ if (deltaRealtime < 0) {
throw new IllegalArgumentException("found non-monotonic realtime");
}
@@ -470,7 +468,7 @@
entry.tag = tag[i];
// find remote row that matches, and subtract
- final int j = value.findIndex(entry.iface, entry.uid, entry.set, entry.tag);
+ final int j = value.findIndexHinted(entry.iface, entry.uid, entry.set, entry.tag, i);
if (j == -1) {
// newly appearing row, return entire value
entry.rxBytes = rxBytes[i];
@@ -485,20 +483,10 @@
entry.txBytes = txBytes[i] - value.txBytes[j];
entry.txPackets = txPackets[i] - value.txPackets[j];
entry.operations = operations[i] - value.operations[j];
- if (enforceMonotonic
- && (entry.rxBytes < 0 || entry.rxPackets < 0 || entry.txBytes < 0
- || entry.txPackets < 0 || entry.operations < 0)) {
- Log.v(TAG, "lhs=" + this);
- Log.v(TAG, "rhs=" + value);
- throw new IllegalArgumentException(
- "found non-monotonic values at lhs[" + i + "] - rhs[" + j + "]");
- }
- if (clampNegative) {
- entry.rxBytes = Math.max(0, entry.rxBytes);
- entry.rxPackets = Math.max(0, entry.rxPackets);
- entry.txBytes = Math.max(0, entry.txBytes);
- entry.txPackets = Math.max(0, entry.txPackets);
- entry.operations = Math.max(0, entry.operations);
+
+ if (entry.rxBytes < 0 || entry.rxPackets < 0 || entry.txBytes < 0
+ || entry.txPackets < 0 || entry.operations < 0) {
+ throw new NonMonotonicException(this, i, value, j);
}
}
@@ -564,6 +552,24 @@
return stats;
}
+ /**
+ * Return all rows except those attributed to the requested UID; doesn't
+ * mutate the original structure.
+ */
+ public NetworkStats withoutUid(int uid) {
+ final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
+
+ Entry entry = new Entry();
+ for (int i = 0; i < size; i++) {
+ entry = getValues(i, entry);
+ if (entry.uid != uid) {
+ stats.addValues(entry);
+ }
+ }
+
+ return stats;
+ }
+
public void dump(String prefix, PrintWriter pw) {
pw.print(prefix);
pw.print("NetworkStats: elapsedRealtime="); pw.println(elapsedRealtime);
@@ -625,4 +631,19 @@
return new NetworkStats[size];
}
};
+
+ public static class NonMonotonicException extends Exception {
+ public final NetworkStats left;
+ public final NetworkStats right;
+ public final int leftIndex;
+ public final int rightIndex;
+
+ public NonMonotonicException(
+ NetworkStats left, int leftIndex, NetworkStats right, int rightIndex) {
+ this.left = checkNotNull(left, "missing left");
+ this.right = checkNotNull(right, "missing right");
+ this.leftIndex = leftIndex;
+ this.rightIndex = rightIndex;
+ }
+ }
}
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index 18eb9f6..cd585b2 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -20,6 +20,7 @@
import android.app.backup.BackupManager;
import android.content.Context;
import android.media.MediaPlayer;
+import android.net.NetworkStats.NonMonotonicException;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -192,12 +193,15 @@
throw new IllegalStateException("not profiling data");
}
- // subtract starting values and return delta
- final NetworkStats profilingStop = getDataLayerSnapshotForUid(context);
- final NetworkStats profilingDelta = profilingStop.subtractClamped(
- sActiveProfilingStart);
- sActiveProfilingStart = null;
- return profilingDelta;
+ try {
+ // subtract starting values and return delta
+ final NetworkStats profilingStop = getDataLayerSnapshotForUid(context);
+ final NetworkStats profilingDelta = profilingStop.subtract(sActiveProfilingStart);
+ sActiveProfilingStart = null;
+ return profilingDelta;
+ } catch (NonMonotonicException e) {
+ throw new RuntimeException(e);
+ }
}
}
diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java
index ee3f23b..41993c4 100644
--- a/core/java/com/android/internal/net/NetworkStatsFactory.java
+++ b/core/java/com/android/internal/net/NetworkStatsFactory.java
@@ -25,12 +25,14 @@
import android.os.SystemClock;
import android.util.Slog;
+import com.android.internal.util.ProcFileReader;
import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
import com.google.android.collect.Sets;
import java.io.BufferedReader;
import java.io.File;
+import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
@@ -107,6 +109,7 @@
final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6);
final NetworkStats.Entry entry = new NetworkStats.Entry();
+ // TODO: transition to ProcFileReader
// TODO: read directly from proc once headers are added
final ArrayList<String> keys = Lists.newArrayList(KEY_IFACE, KEY_ACTIVE, KEY_SNAP_RX_BYTES,
KEY_SNAP_RX_PACKETS, KEY_SNAP_TX_BYTES, KEY_SNAP_TX_PACKETS, KEY_RX_BYTES,
@@ -257,71 +260,58 @@
final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 24);
final NetworkStats.Entry entry = new NetworkStats.Entry();
- // TODO: remove knownLines check once 5087722 verified
- final HashSet<String> knownLines = Sets.newHashSet();
- // TODO: remove lastIdx check once 5270106 verified
- int lastIdx;
+ int idx = 1;
+ int lastIdx = 1;
- final ArrayList<String> keys = Lists.newArrayList();
- final ArrayList<String> values = Lists.newArrayList();
- final HashMap<String, String> parsed = Maps.newHashMap();
-
- BufferedReader reader = null;
- String line = null;
+ ProcFileReader reader = null;
try {
- reader = new BufferedReader(new FileReader(mStatsXtUid));
+ // open and consume header line
+ reader = new ProcFileReader(new FileInputStream(mStatsXtUid));
+ reader.finishLine();
- // parse first line as header
- line = reader.readLine();
- splitLine(line, keys);
- lastIdx = 1;
-
- // parse remaining lines
- while ((line = reader.readLine()) != null) {
- splitLine(line, values);
- parseLine(keys, values, parsed);
-
- if (!knownLines.add(line)) {
- throw new IllegalStateException("duplicate proc entry: " + line);
- }
-
- final int idx = getParsedInt(parsed, KEY_IDX);
+ while (reader.hasMoreData()) {
+ idx = reader.nextInt();
if (idx != lastIdx + 1) {
throw new IllegalStateException(
"inconsistent idx=" + idx + " after lastIdx=" + lastIdx);
}
lastIdx = idx;
- entry.iface = parsed.get(KEY_IFACE);
- entry.uid = getParsedInt(parsed, KEY_UID);
- entry.set = getParsedInt(parsed, KEY_COUNTER_SET);
- entry.tag = kernelToTag(parsed.get(KEY_TAG_HEX));
- entry.rxBytes = getParsedLong(parsed, KEY_RX_BYTES);
- entry.rxPackets = getParsedLong(parsed, KEY_RX_PACKETS);
- entry.txBytes = getParsedLong(parsed, KEY_TX_BYTES);
- entry.txPackets = getParsedLong(parsed, KEY_TX_PACKETS);
+ entry.iface = reader.nextString();
+ entry.tag = kernelToTag(reader.nextString());
+ entry.uid = reader.nextInt();
+ entry.set = reader.nextInt();
+ entry.rxBytes = reader.nextLong();
+ entry.rxPackets = reader.nextLong();
+ entry.txBytes = reader.nextLong();
+ entry.txPackets = reader.nextLong();
if (limitUid == UID_ALL || limitUid == entry.uid) {
stats.addValues(entry);
}
+
+ reader.finishLine();
}
} catch (NullPointerException e) {
- throw new IllegalStateException("problem parsing line: " + line, e);
+ throw new IllegalStateException("problem parsing idx " + idx, e);
} catch (NumberFormatException e) {
- throw new IllegalStateException("problem parsing line: " + line, e);
+ throw new IllegalStateException("problem parsing idx " + idx, e);
} catch (IOException e) {
- throw new IllegalStateException("problem parsing line: " + line, e);
+ throw new IllegalStateException("problem parsing idx " + idx, e);
} finally {
IoUtils.closeQuietly(reader);
}
+
return stats;
}
+ @Deprecated
private static int getParsedInt(HashMap<String, String> parsed, String key) {
final String value = parsed.get(key);
return value != null ? Integer.parseInt(value) : 0;
}
+ @Deprecated
private static long getParsedLong(HashMap<String, String> parsed, String key) {
final String value = parsed.get(key);
return value != null ? Long.parseLong(value) : 0;
@@ -330,6 +320,7 @@
/**
* Split given line into {@link ArrayList}.
*/
+ @Deprecated
private static void splitLine(String line, ArrayList<String> outSplit) {
outSplit.clear();
@@ -343,6 +334,7 @@
* Zip the two given {@link ArrayList} as key and value pairs into
* {@link HashMap}.
*/
+ @Deprecated
private static void parseLine(
ArrayList<String> keys, ArrayList<String> values, HashMap<String, String> outParsed) {
outParsed.clear();
diff --git a/core/java/com/android/internal/util/ProcFileReader.java b/core/java/com/android/internal/util/ProcFileReader.java
new file mode 100644
index 0000000..72e1f0f
--- /dev/null
+++ b/core/java/com/android/internal/util/ProcFileReader.java
@@ -0,0 +1,199 @@
+/*
+ * 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.internal.util;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.Charsets;
+
+/**
+ * Reader that specializes in parsing {@code /proc/} files quickly. Walks
+ * through the stream using a single space {@code ' '} as token separator, and
+ * requires each line boundary to be explicitly acknowledged using
+ * {@link #finishLine()}. Assumes {@link Charsets#US_ASCII} encoding.
+ * <p>
+ * Currently doesn't support formats based on {@code \0}, tabs, or repeated
+ * delimiters.
+ */
+public class ProcFileReader implements Closeable {
+ private final InputStream mStream;
+ private final byte[] mBuffer;
+
+ /** Write pointer in {@link #mBuffer}. */
+ private int mTail;
+ /** Flag when last read token finished current line. */
+ private boolean mLineFinished;
+
+ public ProcFileReader(InputStream stream) throws IOException {
+ this(stream, 4096);
+ }
+
+ public ProcFileReader(InputStream stream, int bufferSize) throws IOException {
+ mStream = stream;
+ mBuffer = new byte[bufferSize];
+
+ // read enough to answer hasMoreData
+ fillBuf();
+ }
+
+ /**
+ * Read more data from {@link #mStream} into internal buffer.
+ */
+ private int fillBuf() throws IOException {
+ final int length = mBuffer.length - mTail;
+ if (length == 0) {
+ throw new IOException("attempting to fill already-full buffer");
+ }
+
+ final int read = mStream.read(mBuffer, mTail, length);
+ if (read != -1) {
+ mTail += read;
+ }
+ return read;
+ }
+
+ /**
+ * Consume number of bytes from beginning of internal buffer. If consuming
+ * all remaining bytes, will attempt to {@link #fillBuf()}.
+ */
+ private void consumeBuf(int count) throws IOException {
+ // TODO: consider moving to read pointer, but for now traceview says
+ // these copies aren't a bottleneck.
+ System.arraycopy(mBuffer, count, mBuffer, 0, mTail - count);
+ mTail -= count;
+ if (mTail == 0) {
+ fillBuf();
+ }
+ }
+
+ /**
+ * Find buffer index of next token delimiter, usually space or newline. Will
+ * fill buffer as needed.
+ */
+ private int nextTokenIndex() throws IOException {
+ if (mLineFinished) {
+ throw new IOException("no tokens remaining on current line");
+ }
+
+ int i = 0;
+ do {
+ // scan forward for token boundary
+ for (; i < mTail; i++) {
+ final byte b = mBuffer[i];
+ if (b == '\n') {
+ mLineFinished = true;
+ return i;
+ }
+ if (b == ' ') {
+ return i;
+ }
+ }
+ } while (fillBuf() > 0);
+
+ throw new IOException("end of stream while looking for token boundary");
+ }
+
+ /**
+ * Check if stream has more data to be parsed.
+ */
+ public boolean hasMoreData() {
+ return mTail > 0;
+ }
+
+ /**
+ * Finish current line, skipping any remaining data.
+ */
+ public void finishLine() throws IOException {
+ // last token already finished line; reset silently
+ if (mLineFinished) {
+ mLineFinished = false;
+ return;
+ }
+
+ int i = 0;
+ do {
+ // scan forward for line boundary and consume
+ for (; i < mTail; i++) {
+ if (mBuffer[i] == '\n') {
+ consumeBuf(i + 1);
+ return;
+ }
+ }
+ } while (fillBuf() > 0);
+
+ throw new IOException("end of stream while looking for line boundary");
+ }
+
+ /**
+ * Parse and return next token as {@link String}.
+ */
+ public String nextString() throws IOException {
+ final int tokenIndex = nextTokenIndex();
+ final String s = new String(mBuffer, 0, tokenIndex, Charsets.US_ASCII);
+ consumeBuf(tokenIndex + 1);
+ return s;
+ }
+
+ /**
+ * Parse and return next token as base-10 encoded {@code long}.
+ */
+ public long nextLong() throws IOException {
+ final int tokenIndex = nextTokenIndex();
+ final boolean negative = mBuffer[0] == '-';
+
+ // TODO: refactor into something like IntegralToString
+ long result = 0;
+ for (int i = negative ? 1 : 0; i < tokenIndex; i++) {
+ final int digit = mBuffer[i] - '0';
+ if (digit < 0 || digit > 9) {
+ throw invalidLong(tokenIndex);
+ }
+
+ // always parse as negative number and apply sign later; this
+ // correctly handles MIN_VALUE which is "larger" than MAX_VALUE.
+ final long next = result * 10 - digit;
+ if (next > result) {
+ throw invalidLong(tokenIndex);
+ }
+ result = next;
+ }
+
+ consumeBuf(tokenIndex + 1);
+ return negative ? result : -result;
+ }
+
+ private NumberFormatException invalidLong(int tokenIndex) {
+ return new NumberFormatException(
+ "invalid long: " + new String(mBuffer, 0, tokenIndex, Charsets.US_ASCII));
+ }
+
+ /**
+ * Parse and return next token as base-10 encoded {@code int}.
+ */
+ public int nextInt() throws IOException {
+ final long value = nextLong();
+ if (value > Integer.MAX_VALUE || value < Integer.MIN_VALUE) {
+ throw new NumberFormatException("parsed value larger than integer");
+ }
+ return (int) value;
+ }
+
+ public void close() throws IOException {
+ mStream.close();
+ }
+}
diff --git a/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java b/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java
index 9eee2f0..0cc883f 100644
--- a/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java
+++ b/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java
@@ -89,7 +89,7 @@
* Ensure that downloading on wifi reports reasonable stats.
*/
@LargeTest
- public void testWifiDownload() {
+ public void testWifiDownload() throws Exception {
assertTrue(setDeviceWifiAndAirplaneMode(mSsid));
NetworkStats pre_test_stats = fetchDataFromProc(mUid);
String ts = Long.toString(System.currentTimeMillis());
@@ -123,7 +123,7 @@
* Ensure that downloading on wifi reports reasonable stats.
*/
@LargeTest
- public void testWifiUpload() {
+ public void testWifiUpload() throws Exception {
assertTrue(setDeviceWifiAndAirplaneMode(mSsid));
// Download a file from the server.
String ts = Long.toString(System.currentTimeMillis());
@@ -160,7 +160,7 @@
* accounting still goes to the app making the call and that the numbers still make sense.
*/
@LargeTest
- public void testWifiDownloadWithDownloadManager() {
+ public void testWifiDownloadWithDownloadManager() throws Exception {
assertTrue(setDeviceWifiAndAirplaneMode(mSsid));
// If we are using the download manager, then the data that is written to /proc/uid_stat/
// is accounted against download manager's uid, since it uses pre-ICS API.
diff --git a/core/tests/coretests/res/raw/xt_qtaguid_extended b/core/tests/coretests/res/raw/xt_qtaguid_extended
deleted file mode 100644
index 2f3b4ec..0000000
--- a/core/tests/coretests/res/raw/xt_qtaguid_extended
+++ /dev/null
@@ -1,3 +0,0 @@
-acct_tag_hex uid_tag_int iface rx_bytes rx_packets tx_bytes tx_packets teleported_goats idx
-0x0 1000 test0 1024 10 2048 20 2716057 2
-0x0000F00D00000000 1000 test0 512 5 512 5 3370318 3
diff --git a/core/tests/coretests/res/raw/xt_qtaguid_typical b/core/tests/coretests/res/raw/xt_qtaguid_typical
index 8df4b1b..c1b0d25 100644
--- a/core/tests/coretests/res/raw/xt_qtaguid_typical
+++ b/core/tests/coretests/res/raw/xt_qtaguid_typical
@@ -1,32 +1,71 @@
-idx iface acct_tag_hex uid_tag_int rx_bytes tx_bytes
-2 wlan0 0x0 0 14615 4270
-3 wlan0 0x0 1000 5175 915
-4 wlan0 0x0 1021 3381 903
-5 wlan0 0x0 10004 333821 53558
-6 wlan0 0x0 10010 4888 37363
-7 wlan0 0x0 10013 52 104
-8 wlan0 0x74182ada00000000 10004 18725 1066
-9 rmnet0 0x0 0 301274 30244
-10 rmnet0 0x0 1000 304 441
-11 rmnet0 0x0 1013 2880 2272
-12 rmnet0 0x0 1021 31407 8430
-13 rmnet0 0x0 10003 32665 3814
-14 rmnet0 0x0 10004 2373141 420112
-15 rmnet0 0x0 10010 870370 1111727
-16 rmnet0 0x0 10013 240 240
-17 rmnet0 0x0 10016 16703 13512
-18 rmnet0 0x0 10017 3990 3269
-19 rmnet0 0x0 10018 474504 14516062
-20 rmnet0 0x0 10019 782804 71077
-21 rmnet0 0x0 10022 70671 49684
-22 rmnet0 0x0 10029 5785354 397159
-23 rmnet0 0x0 10033 2102 1686
-24 rmnet0 0x0 10034 15495464 227694
-25 rmnet0 0x0 10037 31184994 684122
-26 rmnet0 0x0 10051 298687 113485
-27 rmnet0 0x0 10056 29504 20669
-28 rmnet0 0x0 10069 683 596
-29 rmnet0 0x0 10072 34051 12453
-30 rmnet0 0x0 10077 7025393 213866
-31 rmnet0 0x0 10081 354 1178
-32 rmnet0 0x74182ada00000000 10037 28507378 437004
+idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_bytes rx_tcp_packets rx_udp_bytes rx_udp_packets rx_other_bytes rx_other_packets tx_tcp_bytes tx_tcp_packets tx_udp_bytes tx_udp_packets tx_other_bytes tx_other_packets
+2 wlan0 0x0 0 0 18621 96 2898 44 312 6 15897 58 2412 32 312 6 1010 16 1576 22
+3 wlan0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+4 wlan0 0x0 1000 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+5 wlan0 0x0 1000 1 1949 13 1078 14 0 0 1600 10 349 3 0 0 600 10 478 4
+6 wlan0 0x0 10005 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+7 wlan0 0x0 10005 1 32081 38 5315 50 32081 38 0 0 0 0 5315 50 0 0 0 0
+8 wlan0 0x0 10011 0 35777 53 5718 57 0 0 0 0 35777 53 0 0 0 0 5718 57
+9 wlan0 0x0 10011 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+10 wlan0 0x0 10014 0 0 0 1098 13 0 0 0 0 0 0 0 0 0 0 1098 13
+11 wlan0 0x0 10014 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+12 wlan0 0x0 10021 0 562386 573 49228 549 0 0 0 0 562386 573 0 0 0 0 49228 549
+13 wlan0 0x0 10021 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+14 wlan0 0x0 10031 0 3425 5 586 6 0 0 0 0 3425 5 0 0 0 0 586 6
+15 wlan0 0x0 10031 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+16 wlan0 0x7fffff0100000000 10021 0 562386 573 49228 549 0 0 0 0 562386 573 0 0 0 0 49228 549
+17 wlan0 0x7fffff0100000000 10021 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+18 wlan0 0x7fffff0100000000 10031 0 3425 5 586 6 0 0 0 0 3425 5 0 0 0 0 586 6
+19 wlan0 0x7fffff0100000000 10031 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+20 rmnet2 0x0 0 0 547 5 118 2 40 1 243 1 264 3 0 0 62 1 56 1
+21 rmnet2 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+22 rmnet2 0x0 10001 0 1125899906842624 5 984 11 632 5 0 0 0 0 984 11 0 0 0 0
+23 rmnet2 0x0 10001 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+24 rmnet1 0x0 0 0 26736 174 7098 130 7210 97 18382 64 1144 13 2932 64 4054 64 112 2
+25 rmnet1 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+26 rmnet1 0x0 1000 0 75774 77 18038 78 75335 72 439 5 0 0 17668 73 370 5 0 0
+27 rmnet1 0x0 1000 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+28 rmnet1 0x0 10007 0 269945 578 111632 586 269945 578 0 0 0 0 111632 586 0 0 0 0
+29 rmnet1 0x0 10007 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+30 rmnet1 0x0 10011 0 1741256 6918 769778 7019 1741256 6918 0 0 0 0 769778 7019 0 0 0 0
+31 rmnet1 0x0 10011 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+32 rmnet1 0x0 10014 0 0 0 786 12 0 0 0 0 0 0 786 12 0 0 0 0
+33 rmnet1 0x0 10014 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+34 rmnet1 0x0 10021 0 433533 1454 393420 1604 433533 1454 0 0 0 0 393420 1604 0 0 0 0
+35 rmnet1 0x0 10021 1 21215 33 10278 33 21215 33 0 0 0 0 10278 33 0 0 0 0
+36 rmnet1 0x0 10036 0 6310 25 3284 29 6310 25 0 0 0 0 3284 29 0 0 0 0
+37 rmnet1 0x0 10036 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+38 rmnet1 0x0 10047 0 34264 47 3936 34 34264 47 0 0 0 0 3936 34 0 0 0 0
+39 rmnet1 0x0 10047 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+40 rmnet1 0x4e7700000000 10011 0 9187 27 4248 33 9187 27 0 0 0 0 4248 33 0 0 0 0
+41 rmnet1 0x4e7700000000 10011 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+42 rmnet1 0x1000000000000000 10007 0 2109 4 791 4 2109 4 0 0 0 0 791 4 0 0 0 0
+43 rmnet1 0x1000000000000000 10007 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+44 rmnet1 0x1000000400000000 10007 0 9811 22 6286 22 9811 22 0 0 0 0 6286 22 0 0 0 0
+45 rmnet1 0x1000000400000000 10007 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+46 rmnet1 0x1010000000000000 10021 0 164833 426 135392 527 164833 426 0 0 0 0 135392 527 0 0 0 0
+47 rmnet1 0x1010000000000000 10021 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+48 rmnet1 0x1144000400000000 10011 0 10112 18 3334 17 10112 18 0 0 0 0 3334 17 0 0 0 0
+49 rmnet1 0x1144000400000000 10011 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+50 rmnet1 0x1244000400000000 10011 0 1300 3 848 2 1300 3 0 0 0 0 848 2 0 0 0 0
+51 rmnet1 0x1244000400000000 10011 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+52 rmnet1 0x3000000000000000 10007 0 10389 14 1521 12 10389 14 0 0 0 0 1521 12 0 0 0 0
+53 rmnet1 0x3000000000000000 10007 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+54 rmnet1 0x3000000400000000 10007 0 238070 380 93938 404 238070 380 0 0 0 0 93938 404 0 0 0 0
+55 rmnet1 0x3000000400000000 10007 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+56 rmnet1 0x3010000000000000 10021 0 219110 578 227423 676 219110 578 0 0 0 0 227423 676 0 0 0 0
+57 rmnet1 0x3010000000000000 10021 1 742 3 1265 3 742 3 0 0 0 0 1265 3 0 0 0 0
+58 rmnet1 0x3020000000000000 10021 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+59 rmnet1 0x3020000000000000 10021 1 20473 30 9013 30 20473 30 0 0 0 0 9013 30 0 0 0 0
+60 rmnet1 0x3144000400000000 10011 0 43963 92 34414 116 43963 92 0 0 0 0 34414 116 0 0 0 0
+61 rmnet1 0x3144000400000000 10011 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+62 rmnet1 0x3244000400000000 10011 0 3486 8 1520 9 3486 8 0 0 0 0 1520 9 0 0 0 0
+63 rmnet1 0x3244000400000000 10011 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+64 rmnet1 0x7fffff0100000000 10021 0 29102 56 8865 60 29102 56 0 0 0 0 8865 60 0 0 0 0
+65 rmnet1 0x7fffff0100000000 10021 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+66 rmnet1 0x7fffff0300000000 1000 0 995 13 14145 14 995 13 0 0 0 0 14145 14 0 0 0 0
+67 rmnet1 0x7fffff0300000000 1000 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+68 rmnet0 0x0 0 0 4312 49 1288 23 0 0 0 0 4312 49 0 0 0 0 1288 23
+69 rmnet0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+70 rmnet0 0x0 10080 0 22266 30 20976 30 0 0 0 0 22266 30 0 0 0 0 20976 30
+71 rmnet0 0x0 10080 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
diff --git a/core/tests/coretests/res/raw/xt_qtaguid_typical_with_set b/core/tests/coretests/res/raw/xt_qtaguid_typical_with_set
deleted file mode 100644
index b302bb7..0000000
--- a/core/tests/coretests/res/raw/xt_qtaguid_typical_with_set
+++ /dev/null
@@ -1,13 +0,0 @@
-idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_packets rx_tcp_bytes rx_udp_packets rx_udp_bytes rx_other_packets rx_other_bytes tx_tcp_packets tx_tcp_bytes tx_udp_packets tx_udp_bytes tx_other_packets tx_other_bytes
-2 rmnet0 0x0 0 0 14855 82 2804 47 2000 45 12799 35 56 2 676 13 2128 34 0 0
-3 rmnet0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
-4 rmnet0 0x0 1000 0 278102 253 10487 182 277342 243 760 10 0 0 9727 172 760 10 0 0
-5 rmnet0 0x0 1000 1 26033 30 1401 26 25881 28 152 2 0 0 1249 24 152 2 0 0
-6 rmnet0 0x0 10012 0 40524 272 134138 293 40524 272 0 0 0 0 134138 293 0 0 0 0
-7 rmnet0 0x0 10012 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
-8 rmnet0 0x0 10034 0 15791 59 9905 69 15791 59 0 0 0 0 9905 69 0 0 0 0
-9 rmnet0 0x0 10034 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
-10 rmnet0 0x0 10055 0 3602 29 7739 59 3602 29 0 0 0 0 7739 59 0 0 0 0
-11 rmnet0 0x0 10055 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
-12 rmnet0 0x7fff000300000000 1000 0 483 4 1931 6 483 4 0 0 0 0 1931 6 0 0 0 0
-13 rmnet0 0x7fff000300000000 1000 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
diff --git a/core/tests/coretests/src/android/net/NetworkStatsTest.java b/core/tests/coretests/src/android/net/NetworkStatsTest.java
index 7082deb..b37eb46 100644
--- a/core/tests/coretests/src/android/net/NetworkStatsTest.java
+++ b/core/tests/coretests/src/android/net/NetworkStatsTest.java
@@ -51,6 +51,27 @@
assertEquals(-1, stats.findIndex(TEST_IFACE, 6, SET_DEFAULT, TAG_NONE));
}
+ public void testFindIndexHinted() {
+ final NetworkStats stats = new NetworkStats(TEST_START, 3)
+ .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 10)
+ .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 11)
+ .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 12)
+ .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 1024L, 8L, 0L, 0L, 10)
+ .addValues(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D, 0L, 0L, 1024L, 8L, 11)
+ .addValues(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 12);
+
+ // verify that we correctly find across regardless of hinting
+ for (int hint = 0; hint < stats.size(); hint++) {
+ assertEquals(0, stats.findIndexHinted(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, hint));
+ assertEquals(1, stats.findIndexHinted(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, hint));
+ assertEquals(2, stats.findIndexHinted(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, hint));
+ assertEquals(3, stats.findIndexHinted(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, hint));
+ assertEquals(4, stats.findIndexHinted(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D, hint));
+ assertEquals(5, stats.findIndexHinted(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, hint));
+ assertEquals(-1, stats.findIndexHinted(TEST_IFACE, 6, SET_DEFAULT, TAG_NONE, hint));
+ }
+ }
+
public void testAddEntryGrow() throws Exception {
final NetworkStats stats = new NetworkStats(TEST_START, 2);
@@ -257,6 +278,22 @@
assertValues(stats.getTotal(null, ifaces), 1024L, 64L, 0L, 0L, 0L);
}
+ public void testWithoutUid() throws Exception {
+ final NetworkStats before = new NetworkStats(TEST_START, 3)
+ .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L)
+ .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 512L, 32L, 0L, 0L, 0L)
+ .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 64L, 4L, 0L, 0L, 0L)
+ .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 512L, 32L, 0L, 0L, 0L)
+ .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 0L, 0L)
+ .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 128L, 8L, 0L, 0L, 0L);
+
+ final NetworkStats after = before.withoutUid(100);
+ assertEquals(6, before.size());
+ assertEquals(2, after.size());
+ assertValues(after, 0, TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 0L, 0L);
+ assertValues(after, 1, TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 128L, 8L, 0L, 0L, 0L);
+ }
+
private static void assertValues(NetworkStats stats, int index, String iface, int uid, int set,
int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) {
final NetworkStats.Entry entry = stats.getValues(index, null);
diff --git a/core/tests/coretests/src/com/android/internal/net/NetworkStatsFactoryTest.java b/core/tests/coretests/src/com/android/internal/net/NetworkStatsFactoryTest.java
index 8a64f2b..ea94fa9 100644
--- a/core/tests/coretests/src/com/android/internal/net/NetworkStatsFactoryTest.java
+++ b/core/tests/coretests/src/com/android/internal/net/NetworkStatsFactoryTest.java
@@ -71,21 +71,12 @@
stageFile(R.raw.xt_qtaguid_typical, new File(mTestProc, "net/xt_qtaguid/stats"));
final NetworkStats stats = mFactory.readNetworkStatsDetail();
- assertEquals(31, stats.size());
- assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0, 14615L, 4270L);
- assertStatsEntry(stats, "wlan0", 10004, SET_DEFAULT, 0, 333821L, 53558L);
- assertStatsEntry(stats, "wlan0", 10004, SET_DEFAULT, 1947740890, 18725L, 1066L);
- assertStatsEntry(stats, "rmnet0", 10037, SET_DEFAULT, 0, 31184994L, 684122L);
- assertStatsEntry(stats, "rmnet0", 10037, SET_DEFAULT, 1947740890, 28507378L, 437004L);
- }
-
- public void testNetworkStatsDetailExtended() throws Exception {
- stageFile(R.raw.xt_qtaguid_extended, new File(mTestProc, "net/xt_qtaguid/stats"));
-
- final NetworkStats stats = mFactory.readNetworkStatsDetail();
- assertEquals(2, stats.size());
- assertStatsEntry(stats, "test0", 1000, SET_DEFAULT, 0, 1024L, 2048L);
- assertStatsEntry(stats, "test0", 1000, SET_DEFAULT, 0xF00D, 512L, 512L);
+ assertEquals(70, stats.size());
+ assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, 18621L, 2898L);
+ assertStatsEntry(stats, "wlan0", 10011, SET_DEFAULT, 0x0, 35777L, 5718L);
+ assertStatsEntry(stats, "wlan0", 10021, SET_DEFAULT, 0x7fffff01, 562386L, 49228L);
+ assertStatsEntry(stats, "rmnet1", 10021, SET_DEFAULT, 0x30100000, 219110L, 227423L);
+ assertStatsEntry(stats, "rmnet2", 10001, SET_DEFAULT, 0x0, 1125899906842624L, 984L);
}
public void testNetworkStatsSummary() throws Exception {
@@ -149,12 +140,12 @@
}
public void testNetworkStatsWithSet() throws Exception {
- stageFile(R.raw.xt_qtaguid_typical_with_set, new File(mTestProc, "net/xt_qtaguid/stats"));
+ stageFile(R.raw.xt_qtaguid_typical, new File(mTestProc, "net/xt_qtaguid/stats"));
final NetworkStats stats = mFactory.readNetworkStatsDetail();
- assertEquals(12, stats.size());
- assertStatsEntry(stats, "rmnet0", 1000, SET_DEFAULT, 0, 278102L, 253L, 10487L, 182L);
- assertStatsEntry(stats, "rmnet0", 1000, SET_FOREGROUND, 0, 26033L, 30L, 1401L, 26L);
+ assertEquals(70, stats.size());
+ assertStatsEntry(stats, "rmnet1", 10021, SET_DEFAULT, 0x30100000, 219110L, 578L, 227423L, 676L);
+ assertStatsEntry(stats, "rmnet1", 10021, SET_FOREGROUND, 0x30100000, 742L, 3L, 1265L, 3L);
}
public void testNetworkStatsSingle() throws Exception {
diff --git a/core/tests/coretests/src/com/android/internal/util/ProcFileReaderTest.java b/core/tests/coretests/src/com/android/internal/util/ProcFileReaderTest.java
new file mode 100644
index 0000000..386a78d
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/util/ProcFileReaderTest.java
@@ -0,0 +1,163 @@
+/*
+ * 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.internal.util;
+
+import android.test.AndroidTestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.charset.Charsets;
+
+/**
+ * Tests for {@link ProcFileReader}.
+ */
+public class ProcFileReaderTest extends AndroidTestCase {
+
+ public void testEmpty() throws Exception {
+ final ProcFileReader reader = buildReader("");
+
+ assertFalse(reader.hasMoreData());
+ try {
+ reader.finishLine();
+ fail("somehow finished line beyond end of stream?");
+ } catch (IOException e) {
+ // expected
+ }
+ assertFalse(reader.hasMoreData());
+ }
+
+ public void testSingleString() throws Exception {
+ final ProcFileReader reader = buildReader("a\nb\nc\n");
+
+ assertEquals("a", reader.nextString());
+ reader.finishLine();
+ assertTrue(reader.hasMoreData());
+
+ assertEquals("b", reader.nextString());
+ reader.finishLine();
+ assertTrue(reader.hasMoreData());
+
+ assertEquals("c", reader.nextString());
+ reader.finishLine();
+ assertFalse(reader.hasMoreData());
+ }
+
+ public void testMixedNumbersSkip() throws Exception {
+ final ProcFileReader reader = buildReader("1 2 3\n4 abc_def 5 6 7 8 9\n10\n");
+
+ assertEquals(1, reader.nextInt());
+ assertEquals(2, reader.nextInt());
+ assertEquals(3, reader.nextInt());
+ reader.finishLine();
+ assertTrue(reader.hasMoreData());
+
+ assertEquals(4, reader.nextInt());
+ assertEquals("abc_def", reader.nextString());
+ assertEquals(5, reader.nextInt());
+ reader.finishLine();
+ assertTrue(reader.hasMoreData());
+
+ assertEquals(10, reader.nextInt());
+ reader.finishLine();
+ assertFalse(reader.hasMoreData());
+ }
+
+ public void testBufferSize() throws Exception {
+ // read numbers using very small buffer size, exercising fillBuf()
+ final ProcFileReader reader = buildReader("1 21 3 41 5 61 7 81 9 10\n", 3);
+
+ assertEquals(1, reader.nextInt());
+ assertEquals(21, reader.nextInt());
+ assertEquals(3, reader.nextInt());
+ assertEquals(41, reader.nextInt());
+ assertEquals(5, reader.nextInt());
+ assertEquals(61, reader.nextInt());
+ assertEquals(7, reader.nextInt());
+ assertEquals(81, reader.nextInt());
+ assertEquals(9, reader.nextInt());
+ assertEquals(10, reader.nextInt());
+ reader.finishLine();
+ assertFalse(reader.hasMoreData());
+ }
+
+ public void testBlankLines() throws Exception {
+ final ProcFileReader reader = buildReader("1\n\n2\n\n3\n");
+
+ assertEquals(1, reader.nextInt());
+ reader.finishLine();
+ assertTrue(reader.hasMoreData());
+ reader.finishLine();
+ assertTrue(reader.hasMoreData());
+
+ assertEquals(2, reader.nextInt());
+ reader.finishLine();
+ assertTrue(reader.hasMoreData());
+ reader.finishLine();
+ assertTrue(reader.hasMoreData());
+
+ assertEquals(3, reader.nextInt());
+ reader.finishLine();
+ assertFalse(reader.hasMoreData());
+ }
+
+ public void testMinMax() throws Exception {
+ final ProcFileReader reader = buildReader(
+ "1 -1024 9223372036854775807 -9223372036854775808\n");
+
+ assertEquals(1, reader.nextLong());
+ assertEquals(-1024, reader.nextLong());
+ assertEquals(Long.MAX_VALUE, reader.nextLong());
+ assertEquals(Long.MIN_VALUE, reader.nextLong());
+ reader.finishLine();
+ assertFalse(reader.hasMoreData());
+ }
+
+ public void testDelimiterNeverFound() throws Exception {
+ final ProcFileReader reader = buildReader("teststringwithoutdelimiters");
+
+ try {
+ reader.nextString();
+ fail("somehow read a string value?");
+ } catch (IOException e) {
+ // expected
+ assertTrue(e.getMessage().contains("end of stream"));
+ }
+ }
+
+ public void testLargerThanBuffer() throws Exception {
+ // try finishing line larger than buffer
+ final ProcFileReader reader = buildReader("1 teststringlongerthanbuffer\n", 4);
+
+ assertEquals(1, reader.nextLong());
+ try {
+ reader.finishLine();
+ fail("somehow finished line?");
+ } catch (IOException e) {
+ // expected
+ assertTrue(e.getMessage().contains("already-full buffer"));
+ }
+ }
+
+ private static ProcFileReader buildReader(String string) throws IOException {
+ return buildReader(string, 2048);
+ }
+
+ private static ProcFileReader buildReader(String string, int bufferSize) throws IOException {
+ return new ProcFileReader(
+ new ByteArrayInputStream(string.getBytes(Charsets.US_ASCII)), bufferSize);
+ }
+}
diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java
index bdad82a..289ea1f 100644
--- a/services/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -19,7 +19,6 @@
import static android.Manifest.permission.ACCESS_NETWORK_STATE;
import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
import static android.Manifest.permission.DUMP;
-import static android.Manifest.permission.MANAGE_APP_TOKENS;
import static android.Manifest.permission.MANAGE_NETWORK_POLICY;
import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
import static android.Manifest.permission.READ_PHONE_STATE;
@@ -93,6 +92,7 @@
import android.os.INetworkManagementService;
import android.os.IPowerManager;
import android.os.Message;
+import android.os.MessageQueue.IdleHandler;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.provider.Settings;
@@ -1583,6 +1583,11 @@
return intent;
}
+ // @VisibleForTesting
+ public void addIdleHandler(IdleHandler handler) {
+ mHandler.getLooper().getQueue().addIdleHandler(handler);
+ }
+
private static void collectKeys(SparseIntArray source, SparseBooleanArray target) {
final int size = source.size();
for (int i = 0; i < size; i++) {
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
index 789681e..494c655 100644
--- a/services/java/com/android/server/net/NetworkStatsService.java
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -32,7 +32,6 @@
import static android.net.NetworkStats.SET_FOREGROUND;
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL;
-import static android.net.NetworkStatsHistory.randomLong;
import static android.net.NetworkTemplate.buildTemplateMobileAll;
import static android.net.NetworkTemplate.buildTemplateWifi;
import static android.net.TrafficStats.UID_REMOVED;
@@ -49,7 +48,6 @@
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 com.android.internal.util.Preconditions.checkNotNull;
import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT;
import static com.android.server.NetworkManagementSocketTagger.resetKernelUidStats;
@@ -73,9 +71,11 @@
import android.net.NetworkInfo;
import android.net.NetworkState;
import android.net.NetworkStats;
+import android.net.NetworkStats.NonMonotonicException;
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
import android.os.Binder;
+import android.os.DropBoxManager;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
@@ -150,6 +150,12 @@
/** Sample recent usage after each poll event. */
private static final boolean ENABLE_SAMPLE_AFTER_POLL = true;
+ private static final String TAG_NETSTATS_ERROR = "netstats_error";
+
+ private static final String DEV = "dev";
+ private static final String XT = "xt";
+ private static final String UID = "uid";
+
private final Context mContext;
private final INetworkManagementService mNetworkManager;
private final IAlarmManager mAlarmManager;
@@ -160,6 +166,7 @@
private final PowerManager.WakeLock mWakeLock;
private IConnectivityManager mConnManager;
+ private DropBoxManager mDropBox;
// @VisibleForTesting
public static final String ACTION_NETWORK_STATS_POLL =
@@ -306,6 +313,8 @@
// bootstrap initial stats to prevent double-counting later
bootstrapStats();
+
+ mDropBox = (DropBoxManager) mContext.getSystemService(Context.DROPBOX_SERVICE);
}
private void shutdownLocked() {
@@ -621,7 +630,6 @@
// broadcast.
final int uid = intent.getIntExtra(EXTRA_UID, 0);
synchronized (mStatsLock) {
- // TODO: perform one last stats poll for UID
mWakeLock.acquire();
try {
removeUidLocked(uid);
@@ -829,9 +837,9 @@
// persist when enough network data has occurred
final long persistNetworkDevDelta = computeStatsDelta(
- mLastPersistNetworkDevSnapshot, networkDevSnapshot, true).getTotalBytes();
+ mLastPersistNetworkDevSnapshot, networkDevSnapshot, true, DEV).getTotalBytes();
final long persistNetworkXtDelta = computeStatsDelta(
- mLastPersistNetworkXtSnapshot, networkXtSnapshot, true).getTotalBytes();
+ mLastPersistNetworkXtSnapshot, networkXtSnapshot, true, XT).getTotalBytes();
final boolean networkOverThreshold = persistNetworkDevDelta > threshold
|| persistNetworkXtDelta > threshold;
if (persistForce || (persistNetwork && networkOverThreshold)) {
@@ -842,8 +850,8 @@
}
// persist when enough uid data has occurred
- final long persistUidDelta = computeStatsDelta(mLastPersistUidSnapshot, uidSnapshot, true)
- .getTotalBytes();
+ final long persistUidDelta = computeStatsDelta(
+ mLastPersistUidSnapshot, uidSnapshot, true, UID).getTotalBytes();
if (persistForce || (persistUid && persistUidDelta > threshold)) {
writeUidStatsLocked();
mLastPersistUidSnapshot = uidSnapshot;
@@ -872,7 +880,7 @@
final HashSet<String> unknownIface = Sets.newHashSet();
final NetworkStats delta = computeStatsDelta(
- mLastPollNetworkDevSnapshot, networkDevSnapshot, false);
+ mLastPollNetworkDevSnapshot, networkDevSnapshot, false, DEV);
final long timeStart = currentTime - delta.getElapsedRealtime();
NetworkStats.Entry entry = null;
@@ -902,7 +910,7 @@
final HashSet<String> unknownIface = Sets.newHashSet();
final NetworkStats delta = computeStatsDelta(
- mLastPollNetworkXtSnapshot, networkXtSnapshot, false);
+ mLastPollNetworkXtSnapshot, networkXtSnapshot, false, XT);
final long timeStart = currentTime - delta.getElapsedRealtime();
NetworkStats.Entry entry = null;
@@ -931,9 +939,10 @@
private void performUidPollLocked(NetworkStats uidSnapshot, long currentTime) {
ensureUidStatsLoadedLocked();
- final NetworkStats delta = computeStatsDelta(mLastPollUidSnapshot, uidSnapshot, false);
+ final NetworkStats delta = computeStatsDelta(
+ mLastPollUidSnapshot, uidSnapshot, false, UID);
final NetworkStats operationsDelta = computeStatsDelta(
- mLastPollOperationsSnapshot, mOperations, false);
+ mLastPollOperationsSnapshot, mOperations, false, UID);
final long timeStart = currentTime - delta.getElapsedRealtime();
NetworkStats.Entry entry = null;
@@ -1014,6 +1023,9 @@
private void removeUidLocked(int uid) {
ensureUidStatsLoadedLocked();
+ // perform one last poll before removing
+ performPollLocked(FLAG_PERSIST_ALL);
+
final ArrayList<UidStatsKey> knownKeys = Lists.newArrayList();
knownKeys.addAll(mUidStats.keySet());
@@ -1031,6 +1043,10 @@
}
}
+ // clear UID from current stats snapshot
+ mLastPollUidSnapshot = mLastPollUidSnapshot.withoutUid(uid);
+ mLastPollNetworkXtSnapshot = computeNetworkXtSnapshotFromUid(mLastPollUidSnapshot);
+
// clear kernel stats associated with UID
resetKernelUidStats(uid);
@@ -1490,10 +1506,25 @@
* Return the delta between two {@link NetworkStats} snapshots, where {@code
* before} can be {@code null}.
*/
- private static NetworkStats computeStatsDelta(
- NetworkStats before, NetworkStats current, boolean collectStale) {
+ private NetworkStats computeStatsDelta(
+ NetworkStats before, NetworkStats current, boolean collectStale, String type) {
if (before != null) {
- return current.subtractClamped(before);
+ try {
+ return current.subtract(before);
+ } catch (NonMonotonicException e) {
+ Log.w(TAG, "found non-monotonic values; saving to dropbox");
+
+ // record error for debugging
+ final StringBuilder builder = new StringBuilder();
+ builder.append("found non-monotonic " + type + "values at left[" + e.leftIndex
+ + "] - right[" + e.rightIndex + "]\n");
+ builder.append("left=").append(e.left).append('\n');
+ builder.append("right=").append(e.right).append('\n');
+ mDropBox.addText(TAG_NETSTATS_ERROR, builder.toString());
+
+ // return empty delta to avoid recording broken stats
+ return new NetworkStats(0L, 10);
+ }
} else if (collectStale) {
// caller is okay collecting stale stats for first call.
return current;
diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
index e892b5e..368595f 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
@@ -66,6 +66,7 @@
import android.os.Binder;
import android.os.INetworkManagementService;
import android.os.IPowerManager;
+import android.os.MessageQueue.IdleHandler;
import android.test.AndroidTestCase;
import android.test.mock.MockPackageManager;
import android.test.suitebuilder.annotation.LargeTest;
@@ -87,6 +88,7 @@
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import java.util.logging.Handler;
import libcore.io.IoUtils;
@@ -100,6 +102,10 @@
private static final long TEST_START = 1194220800000L;
private static final String TEST_IFACE = "test0";
+ private static final long KB_IN_BYTES = 1024;
+ private static final long MB_IN_BYTES = KB_IN_BYTES * 1024;
+ private static final long GB_IN_BYTES = MB_IN_BYTES * 1024;
+
private static NetworkTemplate sTemplateWifi = NetworkTemplate.buildTemplateWifi();
private BroadcastInterceptingContext mServiceContext;
@@ -255,31 +261,37 @@
mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false);
mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, false);
mProcessObserver.onForegroundActivitiesChanged(PID_3, UID_B, false);
+ waitUntilIdle();
assertFalse(mService.isUidForeground(UID_A));
assertFalse(mService.isUidForeground(UID_B));
// push one of the shared pids into foreground
mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, true);
+ waitUntilIdle();
assertTrue(mService.isUidForeground(UID_A));
assertFalse(mService.isUidForeground(UID_B));
// and swap another uid into foreground
mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, false);
mProcessObserver.onForegroundActivitiesChanged(PID_3, UID_B, true);
+ waitUntilIdle();
assertFalse(mService.isUidForeground(UID_A));
assertTrue(mService.isUidForeground(UID_B));
// push both pid into foreground
mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, true);
mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, true);
+ waitUntilIdle();
assertTrue(mService.isUidForeground(UID_A));
// pull one out, should still be foreground
mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false);
+ waitUntilIdle();
assertTrue(mService.isUidForeground(UID_A));
// pull final pid out, should now be background
mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, false);
+ waitUntilIdle();
assertFalse(mService.isUidForeground(UID_A));
}
@@ -528,13 +540,14 @@
// TODO: consider making strongly ordered mock
expectRemoveInterfaceQuota(TEST_IFACE);
- expectSetInterfaceQuota(TEST_IFACE, 1536L);
+ expectSetInterfaceQuota(TEST_IFACE, (2 * MB_IN_BYTES) - 512);
expectClearNotifications();
future = expectMeteredIfacesChanged(TEST_IFACE);
replay();
- setNetworkPolicies(new NetworkPolicy(sTemplateWifi, CYCLE_DAY, 1024L, 2048L, SNOOZE_NEVER));
+ setNetworkPolicies(new NetworkPolicy(
+ sTemplateWifi, CYCLE_DAY, 1 * MB_IN_BYTES, 2 * MB_IN_BYTES, SNOOZE_NEVER));
future.get();
verifyAndReset();
}
@@ -590,8 +603,8 @@
future = expectMeteredIfacesChanged();
replay();
- setNetworkPolicies(
- new NetworkPolicy(sTemplateWifi, CYCLE_DAY, 1024L, 2048L, SNOOZE_NEVER));
+ setNetworkPolicies(new NetworkPolicy(
+ sTemplateWifi, CYCLE_DAY, 1 * MB_IN_BYTES, 2 * MB_IN_BYTES, SNOOZE_NEVER));
future.get();
verifyAndReset();
}
@@ -609,7 +622,7 @@
.andReturn(stats).atLeastOnce();
expectRemoveInterfaceQuota(TEST_IFACE);
- expectSetInterfaceQuota(TEST_IFACE, 2048L);
+ expectSetInterfaceQuota(TEST_IFACE, 2 * MB_IN_BYTES);
expectClearNotifications();
future = expectMeteredIfacesChanged(TEST_IFACE);
@@ -623,7 +636,7 @@
// go over warning, which should kick notification
incrementCurrentTime(MINUTE_IN_MILLIS);
stats = new NetworkStats(getElapsedRealtime(), 1)
- .addIfaceValues(TEST_IFACE, 1536L, 15L, 0L, 0L);
+ .addIfaceValues(TEST_IFACE, 1536 * KB_IN_BYTES, 15L, 0L, 0L);
{
expectCurrentTime();
@@ -643,7 +656,7 @@
// go over limit, which should kick notification and dialog
incrementCurrentTime(MINUTE_IN_MILLIS);
stats = new NetworkStats(getElapsedRealtime(), 1)
- .addIfaceValues(TEST_IFACE, 5120L, 512L, 0L, 0L);
+ .addIfaceValues(TEST_IFACE, 5 * MB_IN_BYTES, 512L, 0L, 0L);
{
expectCurrentTime();
@@ -799,6 +812,32 @@
}
}
+ private static class IdleFuture extends AbstractFuture<Void> implements IdleHandler {
+ @Override
+ public Void get() throws InterruptedException, ExecutionException {
+ try {
+ return get(5, TimeUnit.SECONDS);
+ } catch (TimeoutException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public boolean queueIdle() {
+ set(null);
+ return false;
+ }
+ }
+
+ /**
+ * Wait until {@link #mService} internal {@link Handler} is idle.
+ */
+ private void waitUntilIdle() throws Exception {
+ final IdleFuture future = new IdleFuture();
+ mService.addIdleHandler(future);
+ future.get();
+ }
+
private static void assertTimeEquals(long expected, long actual) {
if (expected != actual) {
fail("expected " + formatTime(expected) + " but was actually " + formatTime(actual));
diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
index f7dff23..fbc171b 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
@@ -83,6 +83,7 @@
private static final String TAG = "NetworkStatsServiceTest";
private static final String TEST_IFACE = "test0";
+ private static final String TEST_IFACE2 = "test1";
private static final long TEST_START = 1194220800000L;
private static final String IMSI_1 = "310004";
@@ -418,8 +419,12 @@
expectCurrentTime();
expectDefaultSettings();
expectNetworkState(buildMobile3gState(IMSI_2));
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
+ expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ .addIfaceValues(TEST_IFACE, 2048L, 16L, 512L, 4L));
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
+ .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L));
expectNetworkStatsPoll();
replay();
@@ -432,9 +437,11 @@
expectCurrentTime();
expectDefaultSettings();
expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
- .addIfaceValues(TEST_IFACE, 128L, 1L, 1024L, 8L));
+ .addIfaceValues(TEST_IFACE, 2176L, 17L, 1536L, 12L));
expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
- .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 128L, 1L, 1024L, 8L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
+ .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 640L, 5L, 1024L, 8L, 0L)
.addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, 0xFAAD, 128L, 1L, 1024L, 8L, 0L));
expectNetworkStatsPoll();
@@ -499,6 +506,15 @@
// special "removed" bucket.
expectCurrentTime();
expectDefaultSettings();
+ expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ .addIfaceValues(TEST_IFACE, 4128L, 258L, 544L, 34L));
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 16L, 1L, 16L, 1L, 0L)
+ .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 4096L, 258L, 512L, 32L, 0L)
+ .addValues(TEST_IFACE, UID_GREEN, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L));
+ expectNetworkStatsPoll();
+
replay();
final Intent intent = new Intent(ACTION_UID_REMOVED);
intent.putExtra(EXTRA_UID, UID_BLUE);
@@ -553,9 +569,11 @@
incrementCurrentTime(HOUR_IN_MILLIS);
expectCurrentTime();
expectDefaultSettings();
- expectNetworkState(buildMobile4gState());
+ expectNetworkState(buildMobile4gState(TEST_IFACE2));
expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
+ expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L));
expectNetworkStatsPoll();
replay();
@@ -569,8 +587,10 @@
expectDefaultSettings();
expectNetworkStatsSummary(buildEmptyStats());
expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 512L, 4L, 256L, 2L, 0L));
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
+ .addValues(TEST_IFACE2, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
+ .addValues(TEST_IFACE2, UID_RED, SET_DEFAULT, 0xFAAD, 512L, 4L, 256L, 2L, 0L));
expectNetworkStatsPoll();
mService.incrementOperationCount(UID_RED, 0xFAAD, 5);
@@ -625,6 +645,8 @@
expectDefaultSettings();
expectNetworkStatsSummary(buildEmptyStats());
expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 10L, 1L, 10L, 1L, 0L)
.addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 2048L, 16L, 1024L, 8L, 0L));
expectNetworkStatsPoll();
@@ -881,11 +903,11 @@
return new NetworkState(info, prop, null, subscriberId);
}
- private static NetworkState buildMobile4gState() {
+ private static NetworkState buildMobile4gState(String iface) {
final NetworkInfo info = new NetworkInfo(TYPE_WIMAX, 0, null, null);
info.setDetailedState(DetailedState.CONNECTED, null, null);
final LinkProperties prop = new LinkProperties();
- prop.setInterfaceName(TEST_IFACE);
+ prop.setInterfaceName(iface);
return new NetworkState(info, prop, null);
}