Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 808ee95d authored by Jeff Sharkey's avatar Jeff Sharkey Committed by Android (Google) Code Review
Browse files

Merge "Correct proc file reader, optimizations." into ics-mr1

parents 17bd9a22 163e6443
Loading
Loading
Loading
Loading
+71 −50
Original line number Diff line number Diff line
@@ -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 @@ 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.
@@ -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;
            }
        }
@@ -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");
        }

@@ -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];
@@ -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);
                }
            }

@@ -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);
@@ -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;
        }
    }
}
+10 −6
Original line number Diff line number Diff line
@@ -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;

@@ -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);
            }
        }
    }

+29 −37
Original line number Diff line number Diff line
@@ -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;
@@ -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,
@@ -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;
@@ -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();

@@ -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();
+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();
    }
}
+3 −3
Original line number Diff line number Diff line
@@ -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());
@@ -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());
@@ -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