Loading core/java/android/net/NetworkStats.java +71 −50 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -54,6 +55,8 @@ public class NetworkStats implements Parcelable { /** {@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. Loading Loading @@ -295,8 +298,33 @@ public class NetworkStats implements Parcelable { */ 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; } } Loading Loading @@ -423,40 +451,10 @@ public class NetworkStats implements Parcelable { * 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"); } Loading @@ -470,7 +468,7 @@ public class NetworkStats implements Parcelable { 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]; Loading @@ -485,20 +483,10 @@ public class NetworkStats implements Parcelable { 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); } } Loading Loading @@ -564,6 +552,24 @@ public class NetworkStats implements Parcelable { 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); Loading Loading @@ -625,4 +631,19 @@ public class NetworkStats implements Parcelable { 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; } } } core/java/android/net/TrafficStats.java +10 −6 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import android.app.DownloadManager; 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; Loading Loading @@ -192,12 +193,15 @@ public class TrafficStats { throw new IllegalStateException("not profiling data"); } try { // subtract starting values and return delta final NetworkStats profilingStop = getDataLayerSnapshotForUid(context); final NetworkStats profilingDelta = profilingStop.subtractClamped( sActiveProfilingStart); final NetworkStats profilingDelta = profilingStop.subtract(sActiveProfilingStart); sActiveProfilingStart = null; return profilingDelta; } catch (NonMonotonicException e) { throw new RuntimeException(e); } } } Loading core/java/com/android/internal/net/NetworkStatsFactory.java +29 −37 Original line number Diff line number Diff line Loading @@ -25,12 +25,14 @@ import android.net.NetworkStats; 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; Loading Loading @@ -107,6 +109,7 @@ public class NetworkStatsFactory { 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, Loading Loading @@ -257,71 +260,58 @@ public class NetworkStatsFactory { 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)); // 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); // open and consume header line reader = new ProcFileReader(new FileInputStream(mStatsXtUid)); reader.finishLine(); 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; Loading @@ -330,6 +320,7 @@ public class NetworkStatsFactory { /** * Split given line into {@link ArrayList}. */ @Deprecated private static void splitLine(String line, ArrayList<String> outSplit) { outSplit.clear(); Loading @@ -343,6 +334,7 @@ public class NetworkStatsFactory { * 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(); Loading core/java/com/android/internal/util/ProcFileReader.java 0 → 100644 +199 −0 Original line number Diff line number Diff line /* * 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(); } } core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java +3 −3 Original line number Diff line number Diff line Loading @@ -89,7 +89,7 @@ public class BandwidthTest extends InstrumentationTestCase { * 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()); Loading Loading @@ -123,7 +123,7 @@ public class BandwidthTest extends InstrumentationTestCase { * 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()); Loading Loading @@ -160,7 +160,7 @@ public class BandwidthTest extends InstrumentationTestCase { * 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. Loading Loading
core/java/android/net/NetworkStats.java +71 −50 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -54,6 +55,8 @@ public class NetworkStats implements Parcelable { /** {@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. Loading Loading @@ -295,8 +298,33 @@ public class NetworkStats implements Parcelable { */ 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; } } Loading Loading @@ -423,40 +451,10 @@ public class NetworkStats implements Parcelable { * 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"); } Loading @@ -470,7 +468,7 @@ public class NetworkStats implements Parcelable { 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]; Loading @@ -485,20 +483,10 @@ public class NetworkStats implements Parcelable { 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); } } Loading Loading @@ -564,6 +552,24 @@ public class NetworkStats implements Parcelable { 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); Loading Loading @@ -625,4 +631,19 @@ public class NetworkStats implements Parcelable { 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; } } }
core/java/android/net/TrafficStats.java +10 −6 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import android.app.DownloadManager; 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; Loading Loading @@ -192,12 +193,15 @@ public class TrafficStats { throw new IllegalStateException("not profiling data"); } try { // subtract starting values and return delta final NetworkStats profilingStop = getDataLayerSnapshotForUid(context); final NetworkStats profilingDelta = profilingStop.subtractClamped( sActiveProfilingStart); final NetworkStats profilingDelta = profilingStop.subtract(sActiveProfilingStart); sActiveProfilingStart = null; return profilingDelta; } catch (NonMonotonicException e) { throw new RuntimeException(e); } } } Loading
core/java/com/android/internal/net/NetworkStatsFactory.java +29 −37 Original line number Diff line number Diff line Loading @@ -25,12 +25,14 @@ import android.net.NetworkStats; 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; Loading Loading @@ -107,6 +109,7 @@ public class NetworkStatsFactory { 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, Loading Loading @@ -257,71 +260,58 @@ public class NetworkStatsFactory { 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)); // 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); // open and consume header line reader = new ProcFileReader(new FileInputStream(mStatsXtUid)); reader.finishLine(); 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; Loading @@ -330,6 +320,7 @@ public class NetworkStatsFactory { /** * Split given line into {@link ArrayList}. */ @Deprecated private static void splitLine(String line, ArrayList<String> outSplit) { outSplit.clear(); Loading @@ -343,6 +334,7 @@ public class NetworkStatsFactory { * 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(); Loading
core/java/com/android/internal/util/ProcFileReader.java 0 → 100644 +199 −0 Original line number Diff line number Diff line /* * 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(); } }
core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java +3 −3 Original line number Diff line number Diff line Loading @@ -89,7 +89,7 @@ public class BandwidthTest extends InstrumentationTestCase { * 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()); Loading Loading @@ -123,7 +123,7 @@ public class BandwidthTest extends InstrumentationTestCase { * 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()); Loading Loading @@ -160,7 +160,7 @@ public class BandwidthTest extends InstrumentationTestCase { * 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. Loading