Merge "Move network stats to FileRotator pattern."

This commit is contained in:
Jeff Sharkey
2012-01-24 11:49:51 -08:00
committed by Android (Google) Code Review
16 changed files with 1642 additions and 1088 deletions

View File

@@ -102,6 +102,15 @@ public class NetworkStats implements Parcelable {
this.operations = operations;
}
public boolean isNegative() {
return rxBytes < 0 || rxPackets < 0 || txBytes < 0 || txPackets < 0 || operations < 0;
}
public boolean isEmpty() {
return rxBytes == 0 && rxPackets == 0 && txBytes == 0 && txPackets == 0
&& operations == 0;
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
@@ -343,6 +352,7 @@ public class NetworkStats implements Parcelable {
* on matching {@link #uid} and {@link #tag} rows. Ignores {@link #iface},
* since operation counts are at data layer.
*/
@Deprecated
public void spliceOperationsFrom(NetworkStats stats) {
for (int i = 0; i < size; i++) {
final int j = stats.findIndex(IFACE_ALL, uid[i], set[i], tag[i]);
@@ -397,7 +407,7 @@ public class NetworkStats implements Parcelable {
* Return total of all fields represented by this snapshot object.
*/
public Entry getTotal(Entry recycle) {
return getTotal(recycle, null, UID_ALL);
return getTotal(recycle, null, UID_ALL, false);
}
/**
@@ -405,7 +415,7 @@ public class NetworkStats implements Parcelable {
* the requested {@link #uid}.
*/
public Entry getTotal(Entry recycle, int limitUid) {
return getTotal(recycle, null, limitUid);
return getTotal(recycle, null, limitUid, false);
}
/**
@@ -413,7 +423,11 @@ public class NetworkStats implements Parcelable {
* the requested {@link #iface}.
*/
public Entry getTotal(Entry recycle, HashSet<String> limitIface) {
return getTotal(recycle, limitIface, UID_ALL);
return getTotal(recycle, limitIface, UID_ALL, false);
}
public Entry getTotalIncludingTags(Entry recycle) {
return getTotal(recycle, null, UID_ALL, true);
}
/**
@@ -423,7 +437,8 @@ public class NetworkStats implements Parcelable {
* @param limitIface Set of {@link #iface} to include in total; or {@code
* null} to include all ifaces.
*/
private Entry getTotal(Entry recycle, HashSet<String> limitIface, int limitUid) {
private Entry getTotal(
Entry recycle, HashSet<String> limitIface, int limitUid, boolean includeTags) {
final Entry entry = recycle != null ? recycle : new Entry();
entry.iface = IFACE_ALL;
@@ -442,7 +457,7 @@ public class NetworkStats implements Parcelable {
if (matchesUid && matchesIface) {
// skip specific tags, since already counted in TAG_NONE
if (tag[i] != TAG_NONE) continue;
if (tag[i] != TAG_NONE && !includeTags) continue;
entry.rxBytes += rxBytes[i];
entry.rxPackets += rxPackets[i];
@@ -460,7 +475,7 @@ public class NetworkStats implements Parcelable {
* time, and that none of them have disappeared.
*/
public NetworkStats subtract(NetworkStats right) {
return subtract(this, right, null);
return subtract(this, right, null, null);
}
/**
@@ -471,12 +486,12 @@ public class NetworkStats implements Parcelable {
* If counters have rolled backwards, they are clamped to {@code 0} and
* reported to the given {@link NonMonotonicObserver}.
*/
public static NetworkStats subtract(
NetworkStats left, NetworkStats right, NonMonotonicObserver observer) {
public static <C> NetworkStats subtract(
NetworkStats left, NetworkStats right, NonMonotonicObserver<C> observer, C cookie) {
long deltaRealtime = left.elapsedRealtime - right.elapsedRealtime;
if (deltaRealtime < 0) {
if (observer != null) {
observer.foundNonMonotonic(left, -1, right, -1);
observer.foundNonMonotonic(left, -1, right, -1, cookie);
}
deltaRealtime = 0;
}
@@ -510,7 +525,7 @@ public class NetworkStats implements Parcelable {
if (entry.rxBytes < 0 || entry.rxPackets < 0 || entry.txBytes < 0
|| entry.txPackets < 0 || entry.operations < 0) {
if (observer != null) {
observer.foundNonMonotonic(left, i, right, j);
observer.foundNonMonotonic(left, i, right, j, cookie);
}
entry.rxBytes = Math.max(entry.rxBytes, 0);
entry.rxPackets = Math.max(entry.rxPackets, 0);
@@ -663,8 +678,8 @@ public class NetworkStats implements Parcelable {
}
};
public interface NonMonotonicObserver {
public interface NonMonotonicObserver<C> {
public void foundNonMonotonic(
NetworkStats left, int leftIndex, NetworkStats right, int rightIndex);
NetworkStats left, int leftIndex, NetworkStats right, int rightIndex, C cookie);
}
}

View File

@@ -26,16 +26,18 @@ import static android.net.NetworkStatsHistory.DataStreamUtils.writeVarLongArray;
import static android.net.NetworkStatsHistory.Entry.UNKNOWN;
import static android.net.NetworkStatsHistory.ParcelUtils.readLongArray;
import static android.net.NetworkStatsHistory.ParcelUtils.writeLongArray;
import static com.android.internal.util.ArrayUtils.total;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.MathUtils;
import com.android.internal.util.IndentingPrintWriter;
import java.io.CharArrayWriter;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.ProtocolException;
import java.util.Arrays;
import java.util.Random;
@@ -74,6 +76,7 @@ public class NetworkStatsHistory implements Parcelable {
private long[] txBytes;
private long[] txPackets;
private long[] operations;
private long totalBytes;
public static class Entry {
public static final long UNKNOWN = -1;
@@ -106,6 +109,12 @@ public class NetworkStatsHistory implements Parcelable {
if ((fields & FIELD_TX_PACKETS) != 0) txPackets = new long[initialSize];
if ((fields & FIELD_OPERATIONS) != 0) operations = new long[initialSize];
bucketCount = 0;
totalBytes = 0;
}
public NetworkStatsHistory(NetworkStatsHistory existing, long bucketDuration) {
this(bucketDuration, existing.estimateResizeBuckets(bucketDuration));
recordEntireHistory(existing);
}
public NetworkStatsHistory(Parcel in) {
@@ -118,6 +127,7 @@ public class NetworkStatsHistory implements Parcelable {
txPackets = readLongArray(in);
operations = readLongArray(in);
bucketCount = bucketStart.length;
totalBytes = in.readLong();
}
/** {@inheritDoc} */
@@ -130,6 +140,7 @@ public class NetworkStatsHistory implements Parcelable {
writeLongArray(out, txBytes, bucketCount);
writeLongArray(out, txPackets, bucketCount);
writeLongArray(out, operations, bucketCount);
out.writeLong(totalBytes);
}
public NetworkStatsHistory(DataInputStream in) throws IOException {
@@ -144,6 +155,7 @@ public class NetworkStatsHistory implements Parcelable {
txPackets = new long[bucketStart.length];
operations = new long[bucketStart.length];
bucketCount = bucketStart.length;
totalBytes = total(rxBytes) + total(txBytes);
break;
}
case VERSION_ADD_PACKETS:
@@ -158,6 +170,7 @@ public class NetworkStatsHistory implements Parcelable {
txPackets = readVarLongArray(in);
operations = readVarLongArray(in);
bucketCount = bucketStart.length;
totalBytes = total(rxBytes) + total(txBytes);
break;
}
default: {
@@ -207,6 +220,13 @@ public class NetworkStatsHistory implements Parcelable {
}
}
/**
* Return total bytes represented by this history.
*/
public long getTotalBytes() {
return totalBytes;
}
/**
* Return index of bucket that contains or is immediately before the
* requested time.
@@ -266,13 +286,16 @@ public class NetworkStatsHistory implements Parcelable {
* distribute across internal buckets, creating new buckets as needed.
*/
public void recordData(long start, long end, NetworkStats.Entry entry) {
if (entry.rxBytes < 0 || entry.rxPackets < 0 || entry.txBytes < 0 || entry.txPackets < 0
|| entry.operations < 0) {
long rxBytes = entry.rxBytes;
long rxPackets = entry.rxPackets;
long txBytes = entry.txBytes;
long txPackets = entry.txPackets;
long operations = entry.operations;
if (entry.isNegative()) {
throw new IllegalArgumentException("tried recording negative data");
}
if (entry.rxBytes == 0 && entry.rxPackets == 0 && entry.txBytes == 0 && entry.txPackets == 0
&& entry.operations == 0) {
// nothing to record; skip
if (entry.isEmpty()) {
return;
}
@@ -295,21 +318,23 @@ public class NetworkStatsHistory implements Parcelable {
if (overlap <= 0) continue;
// integer math each time is faster than floating point
final long fracRxBytes = entry.rxBytes * overlap / duration;
final long fracRxPackets = entry.rxPackets * overlap / duration;
final long fracTxBytes = entry.txBytes * overlap / duration;
final long fracTxPackets = entry.txPackets * overlap / duration;
final long fracOperations = entry.operations * overlap / duration;
final long fracRxBytes = rxBytes * overlap / duration;
final long fracRxPackets = rxPackets * overlap / duration;
final long fracTxBytes = txBytes * overlap / duration;
final long fracTxPackets = txPackets * overlap / duration;
final long fracOperations = operations * overlap / duration;
addLong(activeTime, i, overlap);
addLong(rxBytes, i, fracRxBytes); entry.rxBytes -= fracRxBytes;
addLong(rxPackets, i, fracRxPackets); entry.rxPackets -= fracRxPackets;
addLong(txBytes, i, fracTxBytes); entry.txBytes -= fracTxBytes;
addLong(txPackets, i, fracTxPackets); entry.txPackets -= fracTxPackets;
addLong(operations, i, fracOperations); entry.operations -= fracOperations;
addLong(this.rxBytes, i, fracRxBytes); rxBytes -= fracRxBytes;
addLong(this.rxPackets, i, fracRxPackets); rxPackets -= fracRxPackets;
addLong(this.txBytes, i, fracTxBytes); txBytes -= fracTxBytes;
addLong(this.txPackets, i, fracTxPackets); txPackets -= fracTxPackets;
addLong(this.operations, i, fracOperations); operations -= fracOperations;
duration -= overlap;
}
totalBytes += entry.rxBytes + entry.txBytes;
}
/**
@@ -394,6 +419,7 @@ public class NetworkStatsHistory implements Parcelable {
/**
* Remove buckets older than requested cutoff.
*/
@Deprecated
public void removeBucketsBefore(long cutoff) {
int i;
for (i = 0; i < bucketCount; i++) {
@@ -415,6 +441,8 @@ public class NetworkStatsHistory implements Parcelable {
if (txPackets != null) txPackets = Arrays.copyOfRange(txPackets, i, length);
if (operations != null) operations = Arrays.copyOfRange(operations, i, length);
bucketCount -= i;
// TODO: subtract removed values from totalBytes
}
}
@@ -527,19 +555,17 @@ public class NetworkStatsHistory implements Parcelable {
return (long) (start + (r.nextFloat() * (end - start)));
}
public void dump(String prefix, PrintWriter pw, boolean fullHistory) {
pw.print(prefix);
public void dump(IndentingPrintWriter pw, boolean fullHistory) {
pw.print("NetworkStatsHistory: bucketDuration="); pw.println(bucketDuration);
pw.increaseIndent();
final int start = fullHistory ? 0 : Math.max(0, bucketCount - 32);
if (start > 0) {
pw.print(prefix);
pw.print(" (omitting "); pw.print(start); pw.println(" buckets)");
pw.print("(omitting "); pw.print(start); pw.println(" buckets)");
}
for (int i = start; i < bucketCount; i++) {
pw.print(prefix);
pw.print(" bucketStart="); pw.print(bucketStart[i]);
pw.print("bucketStart="); pw.print(bucketStart[i]);
if (activeTime != null) { pw.print(" activeTime="); pw.print(activeTime[i]); }
if (rxBytes != null) { pw.print(" rxBytes="); pw.print(rxBytes[i]); }
if (rxPackets != null) { pw.print(" rxPackets="); pw.print(rxPackets[i]); }
@@ -548,12 +574,14 @@ public class NetworkStatsHistory implements Parcelable {
if (operations != null) { pw.print(" operations="); pw.print(operations[i]); }
pw.println();
}
pw.decreaseIndent();
}
@Override
public String toString() {
final CharArrayWriter writer = new CharArrayWriter();
dump("", new PrintWriter(writer), false);
dump(new IndentingPrintWriter(writer, " "), false);
return writer.toString();
}
@@ -579,6 +607,10 @@ public class NetworkStatsHistory implements Parcelable {
if (array != null) array[i] += value;
}
public int estimateResizeBuckets(long newBucketDuration) {
return (int) (size() * getBucketDuration() / newBucketDuration);
}
/**
* Utility methods for interacting with {@link DataInputStream} and
* {@link DataOutputStream}, mostly dealing with writing partial arrays.

View File

@@ -195,7 +195,7 @@ public class TrafficStats {
// subtract starting values and return delta
final NetworkStats profilingStop = getDataLayerSnapshotForUid(context);
final NetworkStats profilingDelta = NetworkStats.subtract(
profilingStop, sActiveProfilingStart, null);
profilingStop, sActiveProfilingStart, null, null);
sActiveProfilingStart = null;
return profilingDelta;
}

View File

@@ -4111,17 +4111,38 @@ public final class Settings {
/** {@hide} */
public static final String NETSTATS_POLL_INTERVAL = "netstats_poll_interval";
/** {@hide} */
public static final String NETSTATS_PERSIST_THRESHOLD = "netstats_persist_threshold";
public static final String NETSTATS_TIME_CACHE_MAX_AGE = "netstats_time_cache_max_age";
/** {@hide} */
public static final String NETSTATS_NETWORK_BUCKET_DURATION = "netstats_network_bucket_duration";
public static final String NETSTATS_GLOBAL_ALERT_BYTES = "netstats_global_alert_bytes";
/** {@hide} */
public static final String NETSTATS_NETWORK_MAX_HISTORY = "netstats_network_max_history";
public static final String NETSTATS_SAMPLE_ENABLED = "netstats_sample_enabled";
/** {@hide} */
public static final String NETSTATS_DEV_BUCKET_DURATION = "netstats_dev_bucket_duration";
/** {@hide} */
public static final String NETSTATS_DEV_PERSIST_BYTES = "netstats_dev_persist_bytes";
/** {@hide} */
public static final String NETSTATS_DEV_ROTATE_AGE = "netstats_dev_rotate_age";
/** {@hide} */
public static final String NETSTATS_DEV_DELETE_AGE = "netstats_dev_delete_age";
/** {@hide} */
public static final String NETSTATS_UID_BUCKET_DURATION = "netstats_uid_bucket_duration";
/** {@hide} */
public static final String NETSTATS_UID_MAX_HISTORY = "netstats_uid_max_history";
public static final String NETSTATS_UID_PERSIST_BYTES = "netstats_uid_persist_bytes";
/** {@hide} */
public static final String NETSTATS_TAG_MAX_HISTORY = "netstats_tag_max_history";
public static final String NETSTATS_UID_ROTATE_AGE = "netstats_uid_rotate_age";
/** {@hide} */
public static final String NETSTATS_UID_DELETE_AGE = "netstats_uid_delete_age";
/** {@hide} */
public static final String NETSTATS_UID_TAG_BUCKET_DURATION = "netstats_uid_tag_bucket_duration";
/** {@hide} */
public static final String NETSTATS_UID_TAG_PERSIST_BYTES = "netstats_uid_tag_persist_bytes";
/** {@hide} */
public static final String NETSTATS_UID_TAG_ROTATE_AGE = "netstats_uid_tag_rotate_age";
/** {@hide} */
public static final String NETSTATS_UID_TAG_DELETE_AGE = "netstats_uid_tag_delete_age";
/** Preferred NTP server. {@hide} */
public static final String NTP_SERVER = "ntp_server";

View File

@@ -142,6 +142,14 @@ public class ArrayUtils
return false;
}
public static long total(long[] array) {
long total = 0;
for (long value : array) {
total += value;
}
return total;
}
/**
* Appends an element to a copy of the array and returns the copy.
* @param array The original array, or null to represent an empty array.

View File

@@ -17,9 +17,9 @@
package com.android.internal.util;
import android.os.FileUtils;
import android.util.Slog;
import com.android.internal.util.FileRotator.Reader;
import com.android.internal.util.FileRotator.Writer;
import com.android.internal.util.FileRotator.Rewriter;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
@@ -41,12 +41,15 @@ import libcore.io.IoUtils;
* Instead of manipulating files directly, users implement interfaces that
* perform operations on {@link InputStream} and {@link OutputStream}. This
* enables atomic rewriting of file contents in
* {@link #combineActive(Reader, Writer, long)}.
* {@link #rewriteActive(Rewriter, long)}.
* <p>
* Users must periodically call {@link #maybeRotate(long)} to perform actual
* rotation. Not inherently thread safe.
*/
public class FileRotator {
private static final String TAG = "FileRotator";
private static final boolean LOGD = true;
private final File mBasePath;
private final String mPrefix;
private final long mRotateAgeMillis;
@@ -72,6 +75,15 @@ public class FileRotator {
public void write(OutputStream out) throws IOException;
}
/**
* External class that reads existing data from given {@link InputStream},
* then writes any modified data to {@link OutputStream}.
*/
public interface Rewriter extends Reader, Writer {
public void reset();
public boolean shouldWrite();
}
/**
* Create a file rotator.
*
@@ -96,6 +108,8 @@ public class FileRotator {
if (!name.startsWith(mPrefix)) continue;
if (name.endsWith(SUFFIX_BACKUP)) {
if (LOGD) Slog.d(TAG, "recovering " + name);
final File backupFile = new File(mBasePath, name);
final File file = new File(
mBasePath, name.substring(0, name.length() - SUFFIX_BACKUP.length()));
@@ -104,6 +118,8 @@ public class FileRotator {
backupFile.renameTo(file);
} else if (name.endsWith(SUFFIX_NO_BACKUP)) {
if (LOGD) Slog.d(TAG, "recovering " + name);
final File noBackupFile = new File(mBasePath, name);
final File file = new File(
mBasePath, name.substring(0, name.length() - SUFFIX_NO_BACKUP.length()));
@@ -116,26 +132,95 @@ public class FileRotator {
}
/**
* Atomically combine data with existing data in currently active file.
* Maintains a backup during write, which is restored if the write fails.
* Delete all files managed by this rotator.
*/
public void combineActive(Reader reader, Writer writer, long currentTimeMillis)
public void deleteAll() {
final FileInfo info = new FileInfo(mPrefix);
for (String name : mBasePath.list()) {
if (!info.parse(name)) continue;
// delete each file that matches parser
new File(mBasePath, name).delete();
}
}
/**
* Process currently active file, first reading any existing data, then
* writing modified data. Maintains a backup during write, which is restored
* if the write fails.
*/
public void rewriteActive(Rewriter rewriter, long currentTimeMillis)
throws IOException {
final String activeName = getActiveName(currentTimeMillis);
rewriteSingle(rewriter, activeName);
}
final File file = new File(mBasePath, activeName);
@Deprecated
public void combineActive(final Reader reader, final Writer writer, long currentTimeMillis)
throws IOException {
rewriteActive(new Rewriter() {
/** {@inheritDoc} */
public void reset() {
// ignored
}
/** {@inheritDoc} */
public void read(InputStream in) throws IOException {
reader.read(in);
}
/** {@inheritDoc} */
public boolean shouldWrite() {
return true;
}
/** {@inheritDoc} */
public void write(OutputStream out) throws IOException {
writer.write(out);
}
}, currentTimeMillis);
}
/**
* Process all files managed by this rotator, usually to rewrite historical
* data. Each file is processed atomically.
*/
public void rewriteAll(Rewriter rewriter) throws IOException {
final FileInfo info = new FileInfo(mPrefix);
for (String name : mBasePath.list()) {
if (!info.parse(name)) continue;
// process each file that matches parser
rewriteSingle(rewriter, name);
}
}
/**
* Process a single file atomically, first reading any existing data, then
* writing modified data. Maintains a backup during write, which is restored
* if the write fails.
*/
private void rewriteSingle(Rewriter rewriter, String name) throws IOException {
if (LOGD) Slog.d(TAG, "rewriting " + name);
final File file = new File(mBasePath, name);
final File backupFile;
rewriter.reset();
if (file.exists()) {
// read existing data
readFile(file, reader);
readFile(file, rewriter);
// skip when rewriter has nothing to write
if (!rewriter.shouldWrite()) return;
// backup existing data during write
backupFile = new File(mBasePath, activeName + SUFFIX_BACKUP);
backupFile = new File(mBasePath, name + SUFFIX_BACKUP);
file.renameTo(backupFile);
try {
writeFile(file, writer);
writeFile(file, rewriter);
// write success, delete backup
backupFile.delete();
@@ -148,11 +233,11 @@ public class FileRotator {
} else {
// create empty backup during write
backupFile = new File(mBasePath, activeName + SUFFIX_NO_BACKUP);
backupFile = new File(mBasePath, name + SUFFIX_NO_BACKUP);
backupFile.createNewFile();
try {
writeFile(file, writer);
writeFile(file, rewriter);
// write success, delete empty backup
backupFile.delete();
@@ -176,6 +261,8 @@ public class FileRotator {
// read file when it overlaps
if (info.startMillis <= matchEndMillis && matchStartMillis <= info.endMillis) {
if (LOGD) Slog.d(TAG, "reading matching " + name);
final File file = new File(mBasePath, name);
readFile(file, reader);
}
@@ -224,16 +311,20 @@ public class FileRotator {
if (!info.parse(name)) continue;
if (info.isActive()) {
// found active file; rotate if old enough
if (info.startMillis < rotateBefore) {
if (info.startMillis <= rotateBefore) {
// found active file; rotate if old enough
if (LOGD) Slog.d(TAG, "rotating " + name);
info.endMillis = currentTimeMillis;
final File file = new File(mBasePath, name);
final File destFile = new File(mBasePath, info.build());
file.renameTo(destFile);
}
} else if (info.endMillis < deleteBefore) {
} else if (info.endMillis <= deleteBefore) {
// found rotated file; delete if old enough
if (LOGD) Slog.d(TAG, "deleting " + name);
final File file = new File(mBasePath, name);
file.delete();
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (C) 2012 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.PrintWriter;
import java.io.Writer;
/**
* Lightweight wrapper around {@link PrintWriter} that automatically indents
* newlines based on internal state. Delays writing indent until first actual
* write on a newline, enabling indent modification after newline.
*/
public class IndentingPrintWriter extends PrintWriter {
private final String mIndent;
private StringBuilder mBuilder = new StringBuilder();
private String mCurrent = new String();
private boolean mEmptyLine = true;
public IndentingPrintWriter(Writer writer, String indent) {
super(writer);
mIndent = indent;
}
public void increaseIndent() {
mBuilder.append(mIndent);
mCurrent = mBuilder.toString();
}
public void decreaseIndent() {
mBuilder.delete(0, mIndent.length());
mCurrent = mBuilder.toString();
}
@Override
public void println() {
super.println();
mEmptyLine = true;
}
@Override
public void write(char[] buf, int offset, int count) {
if (mEmptyLine) {
mEmptyLine = false;
super.print(mCurrent);
}
super.write(buf, offset, count);
}
}

View File

@@ -142,5 +142,5 @@ option java_package com.android.server
# ---------------------------
# NetworkStatsService.java
# ---------------------------
51100 netstats_mobile_sample (dev_rx_bytes|2|2),(dev_tx_bytes|2|2),(dev_rx_pkts|2|1),(dev_tx_pkts|2|1),(xt_rx_bytes|2|2),(xt_tx_bytes|2|2),(xt_rx_pkts|2|1),(xt_tx_pkts|2|1),(uid_rx_bytes|2|2),(uid_tx_bytes|2|2),(uid_rx_pkts|2|1),(uid_tx_pkts|2|1),(trusted_time|2|3),(dev_history_start|2|3)
51101 netstats_wifi_sample (dev_rx_bytes|2|2),(dev_tx_bytes|2|2),(dev_rx_pkts|2|1),(dev_tx_pkts|2|1),(xt_rx_bytes|2|2),(xt_tx_bytes|2|2),(xt_rx_pkts|2|1),(xt_tx_pkts|2|1),(uid_rx_bytes|2|2),(uid_tx_bytes|2|2),(uid_rx_pkts|2|1),(uid_tx_pkts|2|1),(trusted_time|2|3),(dev_history_start|2|3)
51100 netstats_mobile_sample (dev_rx_bytes|2|2),(dev_tx_bytes|2|2),(dev_rx_pkts|2|1),(dev_tx_pkts|2|1),(xt_rx_bytes|2|2),(xt_tx_bytes|2|2),(xt_rx_pkts|2|1),(xt_tx_pkts|2|1),(uid_rx_bytes|2|2),(uid_tx_bytes|2|2),(uid_rx_pkts|2|1),(uid_tx_pkts|2|1),(trusted_time|2|3)
51101 netstats_wifi_sample (dev_rx_bytes|2|2),(dev_tx_bytes|2|2),(dev_rx_pkts|2|1),(dev_tx_pkts|2|1),(xt_rx_bytes|2|2),(xt_tx_bytes|2|2),(xt_rx_pkts|2|1),(xt_tx_pkts|2|1),(uid_rx_bytes|2|2),(uid_tx_bytes|2|2),(uid_rx_pkts|2|1),(uid_tx_pkts|2|1),(trusted_time|2|3)

View File

@@ -1573,6 +1573,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
private long getTotalBytes(NetworkTemplate template, long start, long end) {
try {
return mNetworkStats.getSummaryForNetwork(template, start, end).getTotalBytes();
} catch (RuntimeException e) {
Slog.w(TAG, "problem reading network stats: " + e);
return 0;
} catch (RemoteException e) {
// ignored; service lives in system_server
return 0;

View File

@@ -0,0 +1,510 @@
/*
* Copyright (C) 2012 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.server.net;
import static android.net.NetworkStats.IFACE_ALL;
import static android.net.NetworkStats.SET_ALL;
import static android.net.NetworkStats.SET_DEFAULT;
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL;
import static android.net.TrafficStats.UID_REMOVED;
import android.net.NetworkIdentity;
import android.net.NetworkStats;
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
import android.net.TrafficStats;
import android.text.format.DateUtils;
import com.android.internal.os.AtomicFile;
import com.android.internal.util.FileRotator;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Objects;
import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.ProtocolException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import libcore.io.IoUtils;
/**
* Collection of {@link NetworkStatsHistory}, stored based on combined key of
* {@link NetworkIdentitySet}, UID, set, and tag. Knows how to persist itself.
*/
public class NetworkStatsCollection implements FileRotator.Reader {
private static final String TAG = "NetworkStatsCollection";
/** File header magic number: "ANET" */
private static final int FILE_MAGIC = 0x414E4554;
private static final int VERSION_NETWORK_INIT = 1;
private static final int VERSION_UID_INIT = 1;
private static final int VERSION_UID_WITH_IDENT = 2;
private static final int VERSION_UID_WITH_TAG = 3;
private static final int VERSION_UID_WITH_SET = 4;
private static final int VERSION_UNIFIED_INIT = 16;
private HashMap<Key, NetworkStatsHistory> mStats = Maps.newHashMap();
private long mBucketDuration;
private long mStartMillis;
private long mEndMillis;
private long mTotalBytes;
private boolean mDirty;
public NetworkStatsCollection(long bucketDuration) {
mBucketDuration = bucketDuration;
reset();
}
public void reset() {
mStats.clear();
mStartMillis = Long.MAX_VALUE;
mEndMillis = Long.MIN_VALUE;
mTotalBytes = 0;
mDirty = false;
}
public long getStartMillis() {
return mStartMillis;
}
public long getEndMillis() {
return mEndMillis;
}
public long getTotalBytes() {
return mTotalBytes;
}
public boolean isDirty() {
return mDirty;
}
public void clearDirty() {
mDirty = false;
}
public boolean isEmpty() {
return mStartMillis == Long.MAX_VALUE && mEndMillis == Long.MIN_VALUE;
}
/**
* Combine all {@link NetworkStatsHistory} in this collection which match
* the requested parameters.
*/
public NetworkStatsHistory getHistory(
NetworkTemplate template, int uid, int set, int tag, int fields) {
final NetworkStatsHistory combined = new NetworkStatsHistory(
mBucketDuration, estimateBuckets(), fields);
for (Map.Entry<Key, NetworkStatsHistory> entry : mStats.entrySet()) {
final Key key = entry.getKey();
final boolean setMatches = set == SET_ALL || key.set == set;
if (key.uid == uid && setMatches && key.tag == tag
&& templateMatches(template, key.ident)) {
combined.recordEntireHistory(entry.getValue());
}
}
return combined;
}
/**
* Summarize all {@link NetworkStatsHistory} in this collection which match
* the requested parameters.
*/
public NetworkStats getSummary(NetworkTemplate template, long start, long end) {
final long now = System.currentTimeMillis();
final NetworkStats stats = new NetworkStats(end - start, 24);
final NetworkStats.Entry entry = new NetworkStats.Entry();
NetworkStatsHistory.Entry historyEntry = null;
for (Map.Entry<Key, NetworkStatsHistory> mapEntry : mStats.entrySet()) {
final Key key = mapEntry.getKey();
if (templateMatches(template, key.ident)) {
final NetworkStatsHistory history = mapEntry.getValue();
historyEntry = history.getValues(start, end, now, historyEntry);
entry.iface = IFACE_ALL;
entry.uid = key.uid;
entry.set = key.set;
entry.tag = key.tag;
entry.rxBytes = historyEntry.rxBytes;
entry.rxPackets = historyEntry.rxPackets;
entry.txBytes = historyEntry.txBytes;
entry.txPackets = historyEntry.txPackets;
entry.operations = historyEntry.operations;
if (!entry.isEmpty()) {
stats.combineValues(entry);
}
}
}
return stats;
}
/**
* Record given {@link NetworkStats.Entry} into this collection.
*/
public void recordData(NetworkIdentitySet ident, int uid, int set, int tag, long start,
long end, NetworkStats.Entry entry) {
noteRecordedHistory(start, end, entry.rxBytes + entry.txBytes);
findOrCreateHistory(ident, uid, set, tag).recordData(start, end, entry);
}
/**
* Record given {@link NetworkStatsHistory} into this collection.
*/
private void recordHistory(Key key, NetworkStatsHistory history) {
if (history.size() == 0) return;
noteRecordedHistory(history.getStart(), history.getEnd(), history.getTotalBytes());
final NetworkStatsHistory existing = mStats.get(key);
if (existing != null) {
existing.recordEntireHistory(history);
} else {
mStats.put(key, history);
}
}
/**
* Record all {@link NetworkStatsHistory} contained in the given collection
* into this collection.
*/
public void recordCollection(NetworkStatsCollection another) {
for (Map.Entry<Key, NetworkStatsHistory> entry : another.mStats.entrySet()) {
recordHistory(entry.getKey(), entry.getValue());
}
}
private NetworkStatsHistory findOrCreateHistory(
NetworkIdentitySet ident, int uid, int set, int tag) {
final Key key = new Key(ident, uid, set, tag);
final NetworkStatsHistory existing = mStats.get(key);
// update when no existing, or when bucket duration changed
NetworkStatsHistory updated = null;
if (existing == null) {
updated = new NetworkStatsHistory(mBucketDuration, 10);
} else if (existing.getBucketDuration() != mBucketDuration) {
updated = new NetworkStatsHistory(existing, mBucketDuration);
}
if (updated != null) {
mStats.put(key, updated);
return updated;
} else {
return existing;
}
}
/** {@inheritDoc} */
public void read(InputStream in) throws IOException {
read(new DataInputStream(in));
}
public void read(DataInputStream in) throws IOException {
// verify file magic header intact
final int magic = in.readInt();
if (magic != FILE_MAGIC) {
throw new ProtocolException("unexpected magic: " + magic);
}
final int version = in.readInt();
switch (version) {
case VERSION_UNIFIED_INIT: {
// uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory))
final int identSize = in.readInt();
for (int i = 0; i < identSize; i++) {
final NetworkIdentitySet ident = new NetworkIdentitySet(in);
final int size = in.readInt();
for (int j = 0; j < size; j++) {
final int uid = in.readInt();
final int set = in.readInt();
final int tag = in.readInt();
final Key key = new Key(ident, uid, set, tag);
final NetworkStatsHistory history = new NetworkStatsHistory(in);
recordHistory(key, history);
}
}
break;
}
default: {
throw new ProtocolException("unexpected version: " + version);
}
}
}
public void write(DataOutputStream out) throws IOException {
// cluster key lists grouped by ident
final HashMap<NetworkIdentitySet, ArrayList<Key>> keysByIdent = Maps.newHashMap();
for (Key key : mStats.keySet()) {
ArrayList<Key> keys = keysByIdent.get(key.ident);
if (keys == null) {
keys = Lists.newArrayList();
keysByIdent.put(key.ident, keys);
}
keys.add(key);
}
out.writeInt(FILE_MAGIC);
out.writeInt(VERSION_UNIFIED_INIT);
out.writeInt(keysByIdent.size());
for (NetworkIdentitySet ident : keysByIdent.keySet()) {
final ArrayList<Key> keys = keysByIdent.get(ident);
ident.writeToStream(out);
out.writeInt(keys.size());
for (Key key : keys) {
final NetworkStatsHistory history = mStats.get(key);
out.writeInt(key.uid);
out.writeInt(key.set);
out.writeInt(key.tag);
history.writeToStream(out);
}
}
out.flush();
}
@Deprecated
public void readLegacyNetwork(File file) throws IOException {
final AtomicFile inputFile = new AtomicFile(file);
DataInputStream in = null;
try {
in = new DataInputStream(new BufferedInputStream(inputFile.openRead()));
// verify file magic header intact
final int magic = in.readInt();
if (magic != FILE_MAGIC) {
throw new ProtocolException("unexpected magic: " + magic);
}
final int version = in.readInt();
switch (version) {
case VERSION_NETWORK_INIT: {
// network := size *(NetworkIdentitySet NetworkStatsHistory)
final int size = in.readInt();
for (int i = 0; i < size; i++) {
final NetworkIdentitySet ident = new NetworkIdentitySet(in);
final NetworkStatsHistory history = new NetworkStatsHistory(in);
final Key key = new Key(ident, UID_ALL, SET_ALL, TAG_NONE);
recordHistory(key, history);
}
break;
}
default: {
throw new ProtocolException("unexpected version: " + version);
}
}
} catch (FileNotFoundException e) {
// missing stats is okay, probably first boot
} finally {
IoUtils.closeQuietly(in);
}
}
@Deprecated
public void readLegacyUid(File file, boolean onlyTags) throws IOException {
final AtomicFile inputFile = new AtomicFile(file);
DataInputStream in = null;
try {
in = new DataInputStream(new BufferedInputStream(inputFile.openRead()));
// verify file magic header intact
final int magic = in.readInt();
if (magic != FILE_MAGIC) {
throw new ProtocolException("unexpected magic: " + magic);
}
final int version = in.readInt();
switch (version) {
case VERSION_UID_INIT: {
// uid := size *(UID NetworkStatsHistory)
// drop this data version, since we don't have a good
// mapping into NetworkIdentitySet.
break;
}
case VERSION_UID_WITH_IDENT: {
// uid := size *(NetworkIdentitySet size *(UID NetworkStatsHistory))
// drop this data version, since this version only existed
// for a short time.
break;
}
case VERSION_UID_WITH_TAG:
case VERSION_UID_WITH_SET: {
// uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory))
final int identSize = in.readInt();
for (int i = 0; i < identSize; i++) {
final NetworkIdentitySet ident = new NetworkIdentitySet(in);
final int size = in.readInt();
for (int j = 0; j < size; j++) {
final int uid = in.readInt();
final int set = (version >= VERSION_UID_WITH_SET) ? in.readInt()
: SET_DEFAULT;
final int tag = in.readInt();
final Key key = new Key(ident, uid, set, tag);
final NetworkStatsHistory history = new NetworkStatsHistory(in);
if ((tag == TAG_NONE) != onlyTags) {
recordHistory(key, history);
}
}
}
break;
}
default: {
throw new ProtocolException("unexpected version: " + version);
}
}
} catch (FileNotFoundException e) {
// missing stats is okay, probably first boot
} finally {
IoUtils.closeQuietly(in);
}
}
/**
* Remove any {@link NetworkStatsHistory} attributed to the requested UID,
* moving any {@link NetworkStats#TAG_NONE} series to
* {@link TrafficStats#UID_REMOVED}.
*/
public void removeUid(int uid) {
final ArrayList<Key> knownKeys = Lists.newArrayList();
knownKeys.addAll(mStats.keySet());
// migrate all UID stats into special "removed" bucket
for (Key key : knownKeys) {
if (key.uid == uid) {
// only migrate combined TAG_NONE history
if (key.tag == TAG_NONE) {
final NetworkStatsHistory uidHistory = mStats.get(key);
final NetworkStatsHistory removedHistory = findOrCreateHistory(
key.ident, UID_REMOVED, SET_DEFAULT, TAG_NONE);
removedHistory.recordEntireHistory(uidHistory);
}
mStats.remove(key);
mDirty = true;
}
}
}
private void noteRecordedHistory(long startMillis, long endMillis, long totalBytes) {
if (startMillis < mStartMillis) mStartMillis = startMillis;
if (endMillis > mEndMillis) mEndMillis = endMillis;
mTotalBytes += totalBytes;
mDirty = true;
}
private int estimateBuckets() {
return (int) (Math.min(mEndMillis - mStartMillis, DateUtils.WEEK_IN_MILLIS * 5)
/ mBucketDuration);
}
public void dump(IndentingPrintWriter pw) {
final ArrayList<Key> keys = Lists.newArrayList();
keys.addAll(mStats.keySet());
Collections.sort(keys);
for (Key key : keys) {
pw.print("ident="); pw.print(key.ident.toString());
pw.print(" uid="); pw.print(key.uid);
pw.print(" set="); pw.print(NetworkStats.setToString(key.set));
pw.print(" tag="); pw.println(NetworkStats.tagToString(key.tag));
final NetworkStatsHistory history = mStats.get(key);
pw.increaseIndent();
history.dump(pw, true);
pw.decreaseIndent();
}
}
/**
* Test if given {@link NetworkTemplate} matches any {@link NetworkIdentity}
* in the given {@link NetworkIdentitySet}.
*/
private static boolean templateMatches(NetworkTemplate template, NetworkIdentitySet identSet) {
for (NetworkIdentity ident : identSet) {
if (template.matches(ident)) {
return true;
}
}
return false;
}
private static class Key implements Comparable<Key> {
public final NetworkIdentitySet ident;
public final int uid;
public final int set;
public final int tag;
private final int hashCode;
public Key(NetworkIdentitySet ident, int uid, int set, int tag) {
this.ident = ident;
this.uid = uid;
this.set = set;
this.tag = tag;
hashCode = Objects.hashCode(ident, uid, set, tag);
}
@Override
public int hashCode() {
return hashCode;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Key) {
final Key key = (Key) obj;
return uid == key.uid && set == key.set && tag == key.tag
&& Objects.equal(ident, key.ident);
}
return false;
}
/** {@inheritDoc} */
public int compareTo(Key another) {
return Integer.compare(uid, another.uid);
}
}
}

View File

@@ -0,0 +1,341 @@
/*
* Copyright (C) 2012 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.server.net;
import static android.net.NetworkStats.TAG_NONE;
import static com.android.internal.util.Preconditions.checkNotNull;
import android.net.NetworkStats;
import android.net.NetworkStats.NonMonotonicObserver;
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
import android.net.TrafficStats;
import android.util.Log;
import android.util.Slog;
import com.android.internal.util.FileRotator;
import com.android.internal.util.IndentingPrintWriter;
import com.google.android.collect.Sets;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
import java.util.HashSet;
import java.util.Map;
/**
* Logic to record deltas between periodic {@link NetworkStats} snapshots into
* {@link NetworkStatsHistory} that belong to {@link NetworkStatsCollection}.
* Keeps pending changes in memory until they pass a specific threshold, in
* bytes. Uses {@link FileRotator} for persistence logic.
* <p>
* Not inherently thread safe.
*/
public class NetworkStatsRecorder {
private static final String TAG = "NetworkStatsRecorder";
private static final boolean LOGD = true;
private final FileRotator mRotator;
private final NonMonotonicObserver<String> mObserver;
private final String mCookie;
private final long mBucketDuration;
private final long mPersistThresholdBytes;
private final boolean mOnlyTags;
private NetworkStats mLastSnapshot;
private final NetworkStatsCollection mPending;
private final NetworkStatsCollection mSinceBoot;
private final CombiningRewriter mPendingRewriter;
private WeakReference<NetworkStatsCollection> mComplete;
public NetworkStatsRecorder(FileRotator rotator, NonMonotonicObserver<String> observer,
String cookie, long bucketDuration, long persistThresholdBytes, boolean onlyTags) {
mRotator = checkNotNull(rotator, "missing FileRotator");
mObserver = checkNotNull(observer, "missing NonMonotonicObserver");
mCookie = cookie;
mBucketDuration = bucketDuration;
mPersistThresholdBytes = persistThresholdBytes;
mOnlyTags = onlyTags;
mPending = new NetworkStatsCollection(bucketDuration);
mSinceBoot = new NetworkStatsCollection(bucketDuration);
mPendingRewriter = new CombiningRewriter(mPending);
}
public void resetLocked() {
mLastSnapshot = null;
mPending.reset();
mSinceBoot.reset();
mComplete.clear();
}
public NetworkStats.Entry getTotalSinceBootLocked(NetworkTemplate template) {
return mSinceBoot.getSummary(template, Long.MIN_VALUE, Long.MAX_VALUE).getTotal(null);
}
/**
* Load complete history represented by {@link FileRotator}. Caches
* internally as a {@link WeakReference}, and updated with future
* {@link #recordSnapshotLocked(NetworkStats, Map, long)} snapshots as long
* as reference is valid.
*/
public NetworkStatsCollection getOrLoadCompleteLocked() {
NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
if (complete == null) {
if (LOGD) Slog.d(TAG, "getOrLoadCompleteLocked() reading from disk for " + mCookie);
try {
complete = new NetworkStatsCollection(mBucketDuration);
mRotator.readMatching(complete, Long.MIN_VALUE, Long.MAX_VALUE);
complete.recordCollection(mPending);
mComplete = new WeakReference<NetworkStatsCollection>(complete);
} catch (IOException e) {
Log.wtf(TAG, "problem completely reading network stats", e);
}
}
return complete;
}
/**
* Record any delta that occurred since last {@link NetworkStats} snapshot,
* using the given {@link Map} to identify network interfaces. First
* snapshot is considered bootstrap, and is not counted as delta.
*/
public void recordSnapshotLocked(NetworkStats snapshot,
Map<String, NetworkIdentitySet> ifaceIdent, long currentTimeMillis) {
final HashSet<String> unknownIfaces = Sets.newHashSet();
// assume first snapshot is bootstrap and don't record
if (mLastSnapshot == null) {
mLastSnapshot = snapshot;
return;
}
final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
final NetworkStats delta = NetworkStats.subtract(
snapshot, mLastSnapshot, mObserver, mCookie);
final long end = currentTimeMillis;
final long start = end - delta.getElapsedRealtime();
NetworkStats.Entry entry = null;
for (int i = 0; i < delta.size(); i++) {
entry = delta.getValues(i, entry);
final NetworkIdentitySet ident = ifaceIdent.get(entry.iface);
if (ident == null) {
unknownIfaces.add(entry.iface);
continue;
}
// skip when no delta occured
if (entry.isEmpty()) continue;
// only record tag data when requested
if ((entry.tag == TAG_NONE) != mOnlyTags) {
mPending.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
// also record against boot stats when present
if (mSinceBoot != null) {
mSinceBoot.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
}
// also record against complete dataset when present
if (complete != null) {
complete.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
}
}
}
mLastSnapshot = snapshot;
if (LOGD && unknownIfaces.size() > 0) {
Slog.w(TAG, "unknown interfaces " + unknownIfaces + ", ignoring those stats");
}
}
/**
* Consider persisting any pending deltas, if they are beyond
* {@link #mPersistThresholdBytes}.
*/
public void maybePersistLocked(long currentTimeMillis) {
final long pendingBytes = mPending.getTotalBytes();
if (pendingBytes >= mPersistThresholdBytes) {
forcePersistLocked(currentTimeMillis);
} else {
mRotator.maybeRotate(currentTimeMillis);
}
}
/**
* Force persisting any pending deltas.
*/
public void forcePersistLocked(long currentTimeMillis) {
if (mPending.isDirty()) {
if (LOGD) Slog.d(TAG, "forcePersistLocked() writing for " + mCookie);
try {
mRotator.rewriteActive(mPendingRewriter, currentTimeMillis);
mRotator.maybeRotate(currentTimeMillis);
mPending.reset();
} catch (IOException e) {
Log.wtf(TAG, "problem persisting pending stats", e);
}
}
}
/**
* Remove the given UID from all {@link FileRotator} history, migrating it
* to {@link TrafficStats#UID_REMOVED}.
*/
public void removeUidLocked(int uid) {
try {
// process all existing data to migrate uid
mRotator.rewriteAll(new RemoveUidRewriter(mBucketDuration, uid));
} catch (IOException e) {
Log.wtf(TAG, "problem removing UID " + uid, e);
}
// clear UID from current stats snapshot
if (mLastSnapshot != null) {
mLastSnapshot = mLastSnapshot.withoutUid(uid);
}
}
/**
* Rewriter that will combine current {@link NetworkStatsCollection} values
* with anything read from disk, and write combined set to disk. Clears the
* original {@link NetworkStatsCollection} when finished writing.
*/
private static class CombiningRewriter implements FileRotator.Rewriter {
private final NetworkStatsCollection mCollection;
public CombiningRewriter(NetworkStatsCollection collection) {
mCollection = checkNotNull(collection, "missing NetworkStatsCollection");
}
/** {@inheritDoc} */
public void reset() {
// ignored
}
/** {@inheritDoc} */
public void read(InputStream in) throws IOException {
mCollection.read(in);
}
/** {@inheritDoc} */
public boolean shouldWrite() {
return true;
}
/** {@inheritDoc} */
public void write(OutputStream out) throws IOException {
mCollection.write(new DataOutputStream(out));
mCollection.reset();
}
}
/**
* Rewriter that will remove any {@link NetworkStatsHistory} attributed to
* the requested UID, only writing data back when modified.
*/
public static class RemoveUidRewriter implements FileRotator.Rewriter {
private final NetworkStatsCollection mTemp;
private final int mUid;
public RemoveUidRewriter(long bucketDuration, int uid) {
mTemp = new NetworkStatsCollection(bucketDuration);
mUid = uid;
}
/** {@inheritDoc} */
public void reset() {
mTemp.reset();
}
/** {@inheritDoc} */
public void read(InputStream in) throws IOException {
mTemp.read(in);
mTemp.clearDirty();
mTemp.removeUid(mUid);
}
/** {@inheritDoc} */
public boolean shouldWrite() {
return mTemp.isDirty();
}
/** {@inheritDoc} */
public void write(OutputStream out) throws IOException {
mTemp.write(new DataOutputStream(out));
}
}
public void importLegacyNetworkLocked(File file) throws IOException {
// legacy file still exists; start empty to avoid double importing
mRotator.deleteAll();
final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration);
collection.readLegacyNetwork(file);
final long startMillis = collection.getStartMillis();
final long endMillis = collection.getEndMillis();
if (!collection.isEmpty()) {
// process legacy data, creating active file at starting time, then
// using end time to possibly trigger rotation.
mRotator.rewriteActive(new CombiningRewriter(collection), startMillis);
mRotator.maybeRotate(endMillis);
}
}
public void importLegacyUidLocked(File file) throws IOException {
// legacy file still exists; start empty to avoid double importing
mRotator.deleteAll();
final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration);
collection.readLegacyUid(file, mOnlyTags);
final long startMillis = collection.getStartMillis();
final long endMillis = collection.getEndMillis();
if (!collection.isEmpty()) {
// process legacy data, creating active file at starting time, then
// using end time to possibly trigger rotation.
mRotator.rewriteActive(new CombiningRewriter(collection), startMillis);
mRotator.maybeRotate(endMillis);
}
}
public void dumpLocked(IndentingPrintWriter pw, boolean fullHistory) {
pw.print("Pending bytes: "); pw.println(mPending.getTotalBytes());
if (fullHistory) {
pw.println("Complete history:");
getOrLoadCompleteLocked().dump(pw);
} else {
pw.println("History since boot:");
mSinceBoot.dump(pw);
}
}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

View File

@@ -39,6 +39,7 @@ import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static android.text.format.DateUtils.WEEK_IN_MILLIS;
import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_POLL;
import static org.easymock.EasyMock.anyLong;
import static org.easymock.EasyMock.aryEq;
import static org.easymock.EasyMock.capture;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.eq;
@@ -63,10 +64,12 @@ import android.os.INetworkManagementService;
import android.telephony.TelephonyManager;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.Suppress;
import android.util.TrustedTime;
import com.android.server.net.NetworkStatsService;
import com.android.server.net.NetworkStatsService.NetworkStatsSettings;
import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config;
import org.easymock.Capture;
import org.easymock.EasyMock;
@@ -89,6 +92,10 @@ public class NetworkStatsServiceTest extends AndroidTestCase {
private static final String IMSI_1 = "310004";
private static final String IMSI_2 = "310260";
private static final long KB_IN_BYTES = 1024;
private static final long MB_IN_BYTES = 1024 * KB_IN_BYTES;
private static final long GB_IN_BYTES = 1024 * MB_IN_BYTES;
private static NetworkTemplate sTemplateWifi = buildTemplateWifi();
private static NetworkTemplate sTemplateImsi1 = buildTemplateMobileAll(IMSI_1);
private static NetworkTemplate sTemplateImsi2 = buildTemplateMobileAll(IMSI_2);
@@ -282,13 +289,6 @@ public class NetworkStatsServiceTest extends AndroidTestCase {
mServiceContext.sendBroadcast(new Intent(Intent.ACTION_SHUTDOWN));
verifyAndReset();
// talk with zombie service to assert stats have gone; and assert that
// we persisted them to file.
expectDefaultSettings();
replay();
assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
verifyAndReset();
assertStatsFilesExist(true);
// boot through serviceReady() again
@@ -319,6 +319,8 @@ public class NetworkStatsServiceTest extends AndroidTestCase {
}
// TODO: simulate reboot to test bucket resize
@Suppress
public void testStatsBucketResize() throws Exception {
NetworkStatsHistory history = null;
@@ -602,7 +604,6 @@ public class NetworkStatsServiceTest extends AndroidTestCase {
assertUidTotal(sTemplateImsi1, UID_RED, 1536L, 12L, 1280L, 10L, 10);
verifyAndReset();
}
public void testSummaryForAllUid() throws Exception {
@@ -755,11 +756,15 @@ public class NetworkStatsServiceTest extends AndroidTestCase {
expectDefaultSettings();
expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
.addIfaceValues(TEST_IFACE, 2048L, 16L, 512L, 4L));
expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
.addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L));
final NetworkStats uidStats = new NetworkStats(getElapsedRealtime(), 1)
.addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L);
final String[] tetherIfacePairs = new String[] { TEST_IFACE, "wlan0" };
expectNetworkStatsPoll(tetherIfacePairs, new NetworkStats(getElapsedRealtime(), 1)
.addValues(TEST_IFACE, UID_TETHERING, SET_DEFAULT, TAG_NONE, 1920L, 14L, 384L, 2L, 0L));
final NetworkStats tetherStats = new NetworkStats(getElapsedRealtime(), 1)
.addValues(TEST_IFACE, UID_TETHERING, SET_DEFAULT, TAG_NONE, 1920L, 14L, 384L, 2L, 0L);
expectNetworkStatsUidDetail(uidStats, tetherIfacePairs, tetherStats);
expectNetworkStatsPoll();
replay();
mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
@@ -808,6 +813,9 @@ public class NetworkStatsServiceTest extends AndroidTestCase {
private void expectNetworkState(NetworkState... state) throws Exception {
expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce();
final LinkProperties linkProp = state.length > 0 ? state[0].linkProperties : null;
expect(mConnManager.getActiveLinkProperties()).andReturn(linkProp).atLeastOnce();
}
private void expectNetworkStatsSummary(NetworkStats summary) throws Exception {
@@ -815,23 +823,35 @@ public class NetworkStatsServiceTest extends AndroidTestCase {
}
private void expectNetworkStatsUidDetail(NetworkStats detail) throws Exception {
expectNetworkStatsUidDetail(detail, new String[0], new NetworkStats(0L, 0));
}
private void expectNetworkStatsUidDetail(
NetworkStats detail, String[] tetherIfacePairs, NetworkStats tetherStats)
throws Exception {
expect(mNetManager.getNetworkStatsUidDetail(eq(UID_ALL))).andReturn(detail).atLeastOnce();
// also include tethering details, since they are folded into UID
expect(mConnManager.getTetheredIfacePairs()).andReturn(tetherIfacePairs).atLeastOnce();
expect(mNetManager.getNetworkStatsTethering(aryEq(tetherIfacePairs)))
.andReturn(tetherStats).atLeastOnce();
}
private void expectDefaultSettings() throws Exception {
expectSettings(0L, HOUR_IN_MILLIS, WEEK_IN_MILLIS);
}
private void expectSettings(long persistThreshold, long bucketDuration, long maxHistory)
private void expectSettings(long persistBytes, long bucketDuration, long deleteAge)
throws Exception {
expect(mSettings.getPollInterval()).andReturn(HOUR_IN_MILLIS).anyTimes();
expect(mSettings.getPersistThreshold()).andReturn(persistThreshold).anyTimes();
expect(mSettings.getNetworkBucketDuration()).andReturn(bucketDuration).anyTimes();
expect(mSettings.getNetworkMaxHistory()).andReturn(maxHistory).anyTimes();
expect(mSettings.getUidBucketDuration()).andReturn(bucketDuration).anyTimes();
expect(mSettings.getUidMaxHistory()).andReturn(maxHistory).anyTimes();
expect(mSettings.getTagMaxHistory()).andReturn(maxHistory).anyTimes();
expect(mSettings.getTimeCacheMaxAge()).andReturn(DAY_IN_MILLIS).anyTimes();
expect(mSettings.getGlobalAlertBytes()).andReturn(MB_IN_BYTES).anyTimes();
expect(mSettings.getSampleEnabled()).andReturn(true).anyTimes();
final Config config = new Config(bucketDuration, persistBytes, deleteAge, deleteAge);
expect(mSettings.getDevConfig()).andReturn(config).anyTimes();
expect(mSettings.getUidConfig()).andReturn(config).anyTimes();
expect(mSettings.getUidTagConfig()).andReturn(config).anyTimes();
}
private void expectCurrentTime() throws Exception {
@@ -843,27 +863,16 @@ public class NetworkStatsServiceTest extends AndroidTestCase {
}
private void expectNetworkStatsPoll() throws Exception {
expectNetworkStatsPoll(new String[0], new NetworkStats(getElapsedRealtime(), 0));
}
private void expectNetworkStatsPoll(String[] tetherIfacePairs, NetworkStats tetherStats)
throws Exception {
mNetManager.setGlobalAlert(anyLong());
expectLastCall().anyTimes();
expect(mConnManager.getTetheredIfacePairs()).andReturn(tetherIfacePairs).anyTimes();
expect(mNetManager.getNetworkStatsTethering(eq(tetherIfacePairs)))
.andReturn(tetherStats).anyTimes();
}
private void assertStatsFilesExist(boolean exist) {
final File networkFile = new File(mStatsDir, "netstats.bin");
final File uidFile = new File(mStatsDir, "netstats_uid.bin");
final File basePath = new File(mStatsDir, "netstats");
if (exist) {
assertTrue(networkFile.exists());
assertTrue(uidFile.exists());
assertTrue(basePath.list().length > 0);
} else {
assertFalse(networkFile.exists());
assertFalse(uidFile.exists());
assertTrue(basePath.list().length == 0);
}
}

View File

@@ -0,0 +1,175 @@
/*
* Copyright (C) 2012 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.server.net;
import static android.net.NetworkTemplate.buildTemplateMobileAll;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.NetworkIdentity;
import android.net.NetworkStats;
import android.net.NetworkTemplate;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.MediumTest;
import com.android.frameworks.servicestests.R;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import libcore.io.IoUtils;
import libcore.io.Streams;
/**
* Tests for {@link NetworkStatsCollection}.
*/
@MediumTest
public class NetworkStatsCollectionTest extends AndroidTestCase {
private static final String TEST_FILE = "test.bin";
private static final String TEST_IMSI = "310260000000000";
public void testReadLegacyNetwork() throws Exception {
final File testFile = new File(getContext().getFilesDir(), TEST_FILE);
stageFile(R.raw.netstats_v1, testFile);
final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS);
collection.readLegacyNetwork(testFile);
// verify that history read correctly
assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
636014522L, 709291L, 88037144L, 518820L);
// now export into a unified format
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
collection.write(new DataOutputStream(bos));
// clear structure completely
collection.reset();
assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
0L, 0L, 0L, 0L);
// and read back into structure, verifying that totals are same
collection.read(new ByteArrayInputStream(bos.toByteArray()));
assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
636014522L, 709291L, 88037144L, 518820L);
}
public void testReadLegacyUid() throws Exception {
final File testFile = new File(getContext().getFilesDir(), TEST_FILE);
stageFile(R.raw.netstats_uid_v4, testFile);
final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS);
collection.readLegacyUid(testFile, false);
// verify that history read correctly
assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
637073904L, 711398L, 88342093L, 521006L);
// now export into a unified format
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
collection.write(new DataOutputStream(bos));
// clear structure completely
collection.reset();
assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
0L, 0L, 0L, 0L);
// and read back into structure, verifying that totals are same
collection.read(new ByteArrayInputStream(bos.toByteArray()));
assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
637073904L, 711398L, 88342093L, 521006L);
}
public void testReadLegacyUidTags() throws Exception {
final File testFile = new File(getContext().getFilesDir(), TEST_FILE);
stageFile(R.raw.netstats_uid_v4, testFile);
final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS);
collection.readLegacyUid(testFile, true);
// verify that history read correctly
assertSummaryTotalIncludingTags(collection, buildTemplateMobileAll(TEST_IMSI),
77017831L, 100995L, 35436758L, 92344L);
// now export into a unified format
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
collection.write(new DataOutputStream(bos));
// clear structure completely
collection.reset();
assertSummaryTotalIncludingTags(collection, buildTemplateMobileAll(TEST_IMSI),
0L, 0L, 0L, 0L);
// and read back into structure, verifying that totals are same
collection.read(new ByteArrayInputStream(bos.toByteArray()));
assertSummaryTotalIncludingTags(collection, buildTemplateMobileAll(TEST_IMSI),
77017831L, 100995L, 35436758L, 92344L);
}
/**
* Copy a {@link Resources#openRawResource(int)} into {@link File} for
* testing purposes.
*/
private void stageFile(int rawId, File file) throws Exception {
new File(file.getParent()).mkdirs();
InputStream in = null;
OutputStream out = null;
try {
in = getContext().getResources().openRawResource(rawId);
out = new FileOutputStream(file);
Streams.copy(in, out);
} finally {
IoUtils.closeQuietly(in);
IoUtils.closeQuietly(out);
}
}
public static NetworkIdentitySet buildWifiIdent() {
final NetworkIdentitySet set = new NetworkIdentitySet();
set.add(new NetworkIdentity(ConnectivityManager.TYPE_WIFI, 0, null, false));
return set;
}
private static void assertSummaryTotal(NetworkStatsCollection collection,
NetworkTemplate template, long rxBytes, long rxPackets, long txBytes, long txPackets) {
final NetworkStats.Entry entry = collection.getSummary(
template, Long.MIN_VALUE, Long.MAX_VALUE).getTotal(null);
assertEntry(entry, rxBytes, rxPackets, txBytes, txPackets);
}
private static void assertSummaryTotalIncludingTags(NetworkStatsCollection collection,
NetworkTemplate template, long rxBytes, long rxPackets, long txBytes, long txPackets) {
final NetworkStats.Entry entry = collection.getSummary(
template, Long.MIN_VALUE, Long.MAX_VALUE).getTotalIncludingTags(null);
assertEntry(entry, rxBytes, rxPackets, txBytes, txPackets);
}
private static void assertEntry(
NetworkStats.Entry entry, long rxBytes, long rxPackets, long txBytes, long txPackets) {
assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets);
assertEquals("unexpected txBytes", txBytes, entry.txBytes);
assertEquals("unexpected txPackets", txPackets, entry.txPackets);
}
}