diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java index e8f60b4968..7a1ef6623e 100644 --- a/core/java/android/net/NetworkStats.java +++ b/core/java/android/net/NetworkStats.java @@ -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 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 limitIface, int limitUid) { + private Entry getTotal( + Entry recycle, HashSet 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 NetworkStats subtract( + NetworkStats left, NetworkStats right, NonMonotonicObserver 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 { public void foundNonMonotonic( - NetworkStats left, int leftIndex, NetworkStats right, int rightIndex); + NetworkStats left, int leftIndex, NetworkStats right, int rightIndex, C cookie); } } diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java index 8c01331595..faf8a3f9fe 100644 --- a/core/java/android/net/NetworkStatsHistory.java +++ b/core/java/android/net/NetworkStatsHistory.java @@ -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. diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java index 8bdb669d58..dfdea383d6 100644 --- a/core/java/android/net/TrafficStats.java +++ b/core/java/android/net/TrafficStats.java @@ -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; } diff --git a/services/java/com/android/server/net/NetworkStatsCollection.java b/services/java/com/android/server/net/NetworkStatsCollection.java new file mode 100644 index 0000000000..70038d93ee --- /dev/null +++ b/services/java/com/android/server/net/NetworkStatsCollection.java @@ -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 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 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 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 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> keysByIdent = Maps.newHashMap(); + for (Key key : mStats.keySet()) { + ArrayList 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 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 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 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 { + 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); + } + } +} diff --git a/services/java/com/android/server/net/NetworkStatsRecorder.java b/services/java/com/android/server/net/NetworkStatsRecorder.java new file mode 100644 index 0000000000..e7ba35888d --- /dev/null +++ b/services/java/com/android/server/net/NetworkStatsRecorder.java @@ -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. + *

+ * 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 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 mComplete; + + public NetworkStatsRecorder(FileRotator rotator, NonMonotonicObserver 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(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 ifaceIdent, long currentTimeMillis) { + final HashSet 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); + } + } +} diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java index eeb7fec8e1..c9b79e8e89 100644 --- a/services/java/com/android/server/net/NetworkStatsService.java +++ b/services/java/com/android/server/net/NetworkStatsService.java @@ -34,14 +34,18 @@ import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkStats.UID_ALL; import static android.net.NetworkTemplate.buildTemplateMobileAll; import static android.net.NetworkTemplate.buildTemplateWifi; -import static android.net.TrafficStats.UID_REMOVED; -import static android.provider.Settings.Secure.NETSTATS_NETWORK_BUCKET_DURATION; -import static android.provider.Settings.Secure.NETSTATS_NETWORK_MAX_HISTORY; -import static android.provider.Settings.Secure.NETSTATS_PERSIST_THRESHOLD; +import static android.provider.Settings.Secure.NETSTATS_DEV_BUCKET_DURATION; +import static android.provider.Settings.Secure.NETSTATS_DEV_DELETE_AGE; +import static android.provider.Settings.Secure.NETSTATS_DEV_PERSIST_BYTES; +import static android.provider.Settings.Secure.NETSTATS_DEV_ROTATE_AGE; +import static android.provider.Settings.Secure.NETSTATS_GLOBAL_ALERT_BYTES; import static android.provider.Settings.Secure.NETSTATS_POLL_INTERVAL; -import static android.provider.Settings.Secure.NETSTATS_TAG_MAX_HISTORY; +import static android.provider.Settings.Secure.NETSTATS_SAMPLE_ENABLED; +import static android.provider.Settings.Secure.NETSTATS_TIME_CACHE_MAX_AGE; import static android.provider.Settings.Secure.NETSTATS_UID_BUCKET_DURATION; -import static android.provider.Settings.Secure.NETSTATS_UID_MAX_HISTORY; +import static android.provider.Settings.Secure.NETSTATS_UID_DELETE_AGE; +import static android.provider.Settings.Secure.NETSTATS_UID_PERSIST_BYTES; +import static android.provider.Settings.Secure.NETSTATS_UID_ROTATE_AGE; import static android.telephony.PhoneStateListener.LISTEN_DATA_CONNECTION_STATE; import static android.telephony.PhoneStateListener.LISTEN_NONE; import static android.text.format.DateUtils.DAY_IN_MILLIS; @@ -61,12 +65,10 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; import android.net.IConnectivityManager; import android.net.INetworkManagementEventObserver; import android.net.INetworkStatsService; +import android.net.LinkProperties; import android.net.NetworkIdentity; import android.net.NetworkInfo; import android.net.NetworkState; @@ -74,6 +76,7 @@ import android.net.NetworkStats; import android.net.NetworkStats.NonMonotonicObserver; import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; +import android.net.TrafficStats; import android.os.Binder; import android.os.DropBoxManager; import android.os.Environment; @@ -94,32 +97,18 @@ import android.util.Slog; import android.util.SparseIntArray; import android.util.TrustedTime; -import com.android.internal.os.AtomicFile; -import com.android.internal.util.Objects; +import com.android.internal.util.FileRotator; +import com.android.internal.util.IndentingPrintWriter; import com.android.server.EventLogTags; import com.android.server.connectivity.Tethering; -import com.google.android.collect.Lists; import com.google.android.collect.Maps; -import com.google.android.collect.Sets; -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; import java.io.File; import java.io.FileDescriptor; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; -import java.net.ProtocolException; -import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.Random; - -import libcore.io.IoUtils; /** * Collect and persist detailed network statistics, and provide this data to @@ -127,16 +116,8 @@ import libcore.io.IoUtils; */ public class NetworkStatsService extends INetworkStatsService.Stub { private static final String TAG = "NetworkStats"; - private static final boolean LOGD = false; - private static final boolean LOGV = false; - - /** 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 boolean LOGD = true; + private static final boolean LOGV = true; private static final int MSG_PERFORM_POLL = 1; private static final int MSG_UPDATE_IFACES = 2; @@ -147,9 +128,6 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private static final int FLAG_PERSIST_ALL = FLAG_PERSIST_NETWORK | FLAG_PERSIST_UID; private static final int FLAG_PERSIST_FORCE = 0x100; - /** Sample recent usage after each poll event. */ - private static final boolean ENABLE_SAMPLE_AFTER_POLL = true; - private static final String TAG_NETSTATS_ERROR = "netstats_error"; private final Context mContext; @@ -159,10 +137,12 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private final TelephonyManager mTeleManager; private final NetworkStatsSettings mSettings; + private final File mSystemDir; + private final File mBaseDir; + private final PowerManager.WakeLock mWakeLock; private IConnectivityManager mConnManager; - private DropBoxManager mDropBox; // @VisibleForTesting public static final String ACTION_NETWORK_STATS_POLL = @@ -172,71 +152,76 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private PendingIntent mPollIntent; - // TODO: trim empty history objects entirely - 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 final String PREFIX_DEV = "dev"; + private static final String PREFIX_UID = "uid"; + private static final String PREFIX_UID_TAG = "uid_tag"; + /** * Settings that can be changed externally. */ public interface NetworkStatsSettings { public long getPollInterval(); - public long getPersistThreshold(); - public long getNetworkBucketDuration(); - public long getNetworkMaxHistory(); - public long getUidBucketDuration(); - public long getUidMaxHistory(); - public long getTagMaxHistory(); public long getTimeCacheMaxAge(); + public long getGlobalAlertBytes(); + public boolean getSampleEnabled(); + + public static class Config { + public final long bucketDuration; + public final long persistBytes; + public final long rotateAgeMillis; + public final long deleteAgeMillis; + + public Config(long bucketDuration, long persistBytes, long rotateAgeMillis, + long deleteAgeMillis) { + this.bucketDuration = bucketDuration; + this.persistBytes = persistBytes; + this.rotateAgeMillis = rotateAgeMillis; + this.deleteAgeMillis = deleteAgeMillis; + } + } + + public Config getDevConfig(); + public Config getUidConfig(); + public Config getUidTagConfig(); } private final Object mStatsLock = new Object(); /** Set of currently active ifaces. */ private HashMap mActiveIfaces = Maps.newHashMap(); - /** Set of historical {@code dev} stats for known networks. */ - private HashMap mNetworkDevStats = Maps.newHashMap(); - /** Set of historical {@code xtables} stats for known networks. */ - private HashMap mNetworkXtStats = Maps.newHashMap(); - /** Set of historical {@code xtables} stats for known UIDs. */ - private HashMap mUidStats = Maps.newHashMap(); + /** Current default active iface. */ + private String mActiveIface; - /** Flag if {@link #mNetworkDevStats} have been loaded from disk. */ - private boolean mNetworkStatsLoaded = false; - /** Flag if {@link #mUidStats} have been loaded from disk. */ - private boolean mUidStatsLoaded = false; + private final DropBoxNonMonotonicObserver mNonMonotonicObserver = + new DropBoxNonMonotonicObserver(); - private NetworkStats mLastPollNetworkDevSnapshot; - private NetworkStats mLastPollNetworkXtSnapshot; - private NetworkStats mLastPollUidSnapshot; - private NetworkStats mLastPollOperationsSnapshot; + private NetworkStatsRecorder mDevRecorder; + private NetworkStatsRecorder mUidRecorder; + private NetworkStatsRecorder mUidTagRecorder; - private NetworkStats mLastPersistNetworkDevSnapshot; - private NetworkStats mLastPersistNetworkXtSnapshot; - private NetworkStats mLastPersistUidSnapshot; + /** Cached {@link #mDevRecorder} stats. */ + private NetworkStatsCollection mDevStatsCached; /** Current counter sets for each UID. */ private SparseIntArray mActiveUidCounterSet = new SparseIntArray(); /** Data layer operation counters for splicing into other structures. */ - private NetworkStats mOperations = new NetworkStats(0L, 10); + private NetworkStats mUidOperations = new NetworkStats(0L, 10); private final HandlerThread mHandlerThread; private final Handler mHandler; - private final AtomicFile mNetworkDevFile; - private final AtomicFile mNetworkXtFile; - private final AtomicFile mUidFile; - public NetworkStatsService( Context context, INetworkManagementService networkManager, IAlarmManager alarmManager) { this(context, networkManager, alarmManager, NtpTrustedTime.getInstance(context), - getSystemDir(), new DefaultNetworkStatsSettings(context)); + getDefaultSystemDir(), new DefaultNetworkStatsSettings(context)); } - private static File getSystemDir() { + private static File getDefaultSystemDir() { return new File(Environment.getDataDirectory(), "system"); } @@ -258,9 +243,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { mHandlerThread.start(); mHandler = new Handler(mHandlerThread.getLooper(), mHandlerCallback); - mNetworkDevFile = new AtomicFile(new File(systemDir, "netstats.bin")); - mNetworkXtFile = new AtomicFile(new File(systemDir, "netstats_xt.bin")); - mUidFile = new AtomicFile(new File(systemDir, "netstats_uid.bin")); + mSystemDir = checkNotNull(systemDir); + mBaseDir = new File(systemDir, "netstats"); + mBaseDir.mkdirs(); } public void bindConnectivityManager(IConnectivityManager connManager) { @@ -273,17 +258,22 @@ public class NetworkStatsService extends INetworkStatsService.Stub { return; } - synchronized (mStatsLock) { - // read historical network stats from disk, since policy service - // might need them right away. we delay loading detailed UID stats - // until actually needed. - readNetworkDevStatsLocked(); - readNetworkXtStatsLocked(); - mNetworkStatsLoaded = true; - } + // create data recorders along with historical rotators + mDevRecorder = buildRecorder(PREFIX_DEV, mSettings.getDevConfig(), false); + mUidRecorder = buildRecorder(PREFIX_UID, mSettings.getUidConfig(), false); + mUidTagRecorder = buildRecorder(PREFIX_UID_TAG, mSettings.getUidTagConfig(), true); - // bootstrap initial stats to prevent double-counting later - bootstrapStats(); + synchronized (mStatsLock) { + // upgrade any legacy stats, migrating them to rotated files + maybeUpgradeLegacyStatsLocked(); + + // read historical network stats from disk, since policy service + // might need them right away. + mDevStatsCached = mDevRecorder.getOrLoadCompleteLocked(); + + // bootstrap initial stats to prevent double-counting later + bootstrapStatsLocked(); + } // watch for network interfaces to be claimed final IntentFilter connFilter = new IntentFilter(CONNECTIVITY_ACTION_IMMEDIATE); @@ -317,8 +307,14 @@ public class NetworkStatsService extends INetworkStatsService.Stub { registerPollAlarmLocked(); registerGlobalAlert(); + } - mDropBox = (DropBoxManager) mContext.getSystemService(Context.DROPBOX_SERVICE); + private NetworkStatsRecorder buildRecorder( + String prefix, NetworkStatsSettings.Config config, boolean includeTags) { + return new NetworkStatsRecorder( + new FileRotator(mBaseDir, prefix, config.rotateAgeMillis, config.deleteAgeMillis), + mNonMonotonicObserver, prefix, config.bucketDuration, config.persistBytes, + includeTags); } private void shutdownLocked() { @@ -330,18 +326,44 @@ public class NetworkStatsService extends INetworkStatsService.Stub { mTeleManager.listen(mPhoneListener, LISTEN_NONE); - if (mNetworkStatsLoaded) { - writeNetworkDevStatsLocked(); - writeNetworkXtStatsLocked(); + final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis() + : System.currentTimeMillis(); + + // persist any pending stats + mDevRecorder.forcePersistLocked(currentTime); + mUidRecorder.forcePersistLocked(currentTime); + mUidTagRecorder.forcePersistLocked(currentTime); + + mDevRecorder = null; + mUidRecorder = null; + mUidTagRecorder = null; + + mDevStatsCached = null; + } + + private void maybeUpgradeLegacyStatsLocked() { + File file; + try { + file = new File(mSystemDir, "netstats.bin"); + if (file.exists()) { + mDevRecorder.importLegacyNetworkLocked(file); + file.delete(); + } + + file = new File(mSystemDir, "netstats_xt.bin"); + if (file.exists()) { + file.delete(); + } + + file = new File(mSystemDir, "netstats_uid.bin"); + if (file.exists()) { + mUidRecorder.importLegacyUidLocked(file); + mUidTagRecorder.importLegacyUidLocked(file); + file.delete(); + } + } catch (IOException e) { + Log.wtf(TAG, "problem during legacy upgrade", e); } - if (mUidStatsLoaded) { - writeUidStatsLocked(); - } - mNetworkDevStats.clear(); - mNetworkXtStats.clear(); - mUidStats.clear(); - mNetworkStatsLoaded = false; - mUidStatsLoaded = false; } /** @@ -372,7 +394,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { */ private void registerGlobalAlert() { try { - final long alertBytes = mSettings.getPersistThreshold(); + final long alertBytes = mSettings.getGlobalAlertBytes(); mNetworkManager.setGlobalAlert(alertBytes); } catch (IllegalStateException e) { Slog.w(TAG, "problem registering for global alert: " + e); @@ -383,161 +405,39 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @Override public NetworkStatsHistory getHistoryForNetwork(NetworkTemplate template, int fields) { - mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG); - return getHistoryForNetworkDev(template, fields); + return mDevStatsCached.getHistory(template, UID_ALL, SET_ALL, TAG_NONE, fields); } - private NetworkStatsHistory getHistoryForNetworkDev(NetworkTemplate template, int fields) { - return getHistoryForNetwork(template, fields, mNetworkDevStats); - } - - private NetworkStatsHistory getHistoryForNetworkXt(NetworkTemplate template, int fields) { - return getHistoryForNetwork(template, fields, mNetworkXtStats); - } - - private NetworkStatsHistory getHistoryForNetwork(NetworkTemplate template, int fields, - HashMap source) { - synchronized (mStatsLock) { - // combine all interfaces that match template - final NetworkStatsHistory combined = new NetworkStatsHistory( - mSettings.getNetworkBucketDuration(), estimateNetworkBuckets(), fields); - for (NetworkIdentitySet ident : source.keySet()) { - if (templateMatches(template, ident)) { - final NetworkStatsHistory history = source.get(ident); - if (history != null) { - combined.recordEntireHistory(history); - } - } - } - return combined; - } + @Override + public NetworkStats getSummaryForNetwork(NetworkTemplate template, long start, long end) { + return mDevStatsCached.getSummary(template, start, end); } @Override public NetworkStatsHistory getHistoryForUid( NetworkTemplate template, int uid, int set, int tag, int fields) { - mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG); - - synchronized (mStatsLock) { - ensureUidStatsLoadedLocked(); - - // combine all interfaces that match template - final NetworkStatsHistory combined = new NetworkStatsHistory( - mSettings.getUidBucketDuration(), estimateUidBuckets(), fields); - for (UidStatsKey key : mUidStats.keySet()) { - final boolean setMatches = set == SET_ALL || key.set == set; - if (templateMatches(template, key.ident) && key.uid == uid && setMatches - && key.tag == tag) { - final NetworkStatsHistory history = mUidStats.get(key); - combined.recordEntireHistory(history); - } - } - - return combined; + // TODO: transition to stats sessions to avoid WeakReferences + if (tag == TAG_NONE) { + return mUidRecorder.getOrLoadCompleteLocked().getHistory( + template, uid, set, tag, fields); + } else { + return mUidTagRecorder.getOrLoadCompleteLocked().getHistory( + template, uid, set, tag, fields); } } - @Override - public NetworkStats getSummaryForNetwork(NetworkTemplate template, long start, long end) { - mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG); - return getSummaryForNetworkDev(template, start, end); - } - - private NetworkStats getSummaryForNetworkDev(NetworkTemplate template, long start, long end) { - return getSummaryForNetwork(template, start, end, mNetworkDevStats); - } - - private NetworkStats getSummaryForNetworkXt(NetworkTemplate template, long start, long end) { - return getSummaryForNetwork(template, start, end, mNetworkXtStats); - } - - private NetworkStats getSummaryForNetwork(NetworkTemplate template, long start, long end, - HashMap source) { - synchronized (mStatsLock) { - // use system clock to be externally consistent - final long now = System.currentTimeMillis(); - - final NetworkStats stats = new NetworkStats(end - start, 1); - final NetworkStats.Entry entry = new NetworkStats.Entry(); - NetworkStatsHistory.Entry historyEntry = null; - - // combine total from all interfaces that match template - for (NetworkIdentitySet ident : source.keySet()) { - if (templateMatches(template, ident)) { - final NetworkStatsHistory history = source.get(ident); - historyEntry = history.getValues(start, end, now, historyEntry); - - entry.iface = IFACE_ALL; - entry.uid = UID_ALL; - entry.tag = TAG_NONE; - entry.rxBytes = historyEntry.rxBytes; - entry.rxPackets = historyEntry.rxPackets; - entry.txBytes = historyEntry.txBytes; - entry.txPackets = historyEntry.txPackets; - - stats.combineValues(entry); - } - } - - return stats; - } - } - - private long getHistoryStartLocked( - NetworkTemplate template, HashMap source) { - long start = Long.MAX_VALUE; - for (NetworkIdentitySet ident : source.keySet()) { - if (templateMatches(template, ident)) { - final NetworkStatsHistory history = source.get(ident); - start = Math.min(start, history.getStart()); - } - } - return start; - } - @Override public NetworkStats getSummaryForAllUid( NetworkTemplate template, long start, long end, boolean includeTags) { - mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG); - - synchronized (mStatsLock) { - ensureUidStatsLoadedLocked(); - - // use system clock to be externally consistent - 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 (UidStatsKey key : mUidStats.keySet()) { - if (templateMatches(template, key.ident)) { - // always include summary under TAG_NONE, and include - // other tags when requested. - if (key.tag == TAG_NONE || includeTags) { - final NetworkStatsHistory history = mUidStats.get(key); - 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.rxBytes > 0 || entry.rxPackets > 0 || entry.txBytes > 0 - || entry.txPackets > 0 || entry.operations > 0) { - stats.combineValues(entry); - } - } - } - } - - return stats; + // TODO: transition to stats sessions to avoid WeakReferences + final NetworkStats stats = mUidRecorder.getOrLoadCompleteLocked().getSummary( + template, start, end); + if (includeTags) { + final NetworkStats tagStats = mUidTagRecorder.getOrLoadCompleteLocked().getSummary( + template, start, end); + stats.combineAllValues(tagStats); } + return stats; } @Override @@ -567,7 +467,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } // splice in operation counts - dataLayer.spliceOperationsFrom(mOperations); + dataLayer.spliceOperationsFrom(mUidOperations); return dataLayer; } @@ -586,8 +486,10 @@ public class NetworkStatsService extends INetworkStatsService.Stub { synchronized (mStatsLock) { final int set = mActiveUidCounterSet.get(uid, SET_DEFAULT); - mOperations.combineValues(IFACE_ALL, uid, set, tag, 0L, 0L, 0L, 0L, operationCount); - mOperations.combineValues(IFACE_ALL, uid, set, TAG_NONE, 0L, 0L, 0L, 0L, operationCount); + mUidOperations.combineValues( + mActiveIface, uid, set, tag, 0L, 0L, 0L, 0L, operationCount); + mUidOperations.combineValues( + mActiveIface, uid, set, TAG_NONE, 0L, 0L, 0L, 0L, operationCount); } } @@ -755,13 +657,17 @@ public class NetworkStatsService extends INetworkStatsService.Stub { performPollLocked(FLAG_PERSIST_NETWORK); final NetworkState[] states; + final LinkProperties activeLink; try { states = mConnManager.getAllNetworkState(); + activeLink = mConnManager.getActiveLinkProperties(); } catch (RemoteException e) { // ignored; service lives in system_server return; } + mActiveIface = activeLink != null ? activeLink.getInterfaceName() : null; + // rebuild active interfaces based on connected networks mActiveIfaces.clear(); @@ -785,12 +691,20 @@ public class NetworkStatsService extends INetworkStatsService.Stub { * Bootstrap initial stats snapshot, usually during {@link #systemReady()} * so we have baseline values without double-counting. */ - private void bootstrapStats() { + private void bootstrapStatsLocked() { + final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis() + : System.currentTimeMillis(); + try { - mLastPollUidSnapshot = mNetworkManager.getNetworkStatsUidDetail(UID_ALL); - mLastPollNetworkDevSnapshot = mNetworkManager.getNetworkStatsSummary(); - mLastPollNetworkXtSnapshot = computeNetworkXtSnapshotFromUid(mLastPollUidSnapshot); - mLastPollOperationsSnapshot = new NetworkStats(0L, 0); + // snapshot and record current counters; read UID stats first to + // avoid overcounting dev stats. + final NetworkStats uidSnapshot = getNetworkStatsUidDetail(); + final NetworkStats devSnapshot = getNetworkStatsSummary(); + + mDevRecorder.recordSnapshotLocked(devSnapshot, mActiveIfaces, currentTime); + mUidRecorder.recordSnapshotLocked(uidSnapshot, mActiveIfaces, currentTime); + mUidTagRecorder.recordSnapshotLocked(uidSnapshot, mActiveIfaces, currentTime); + } catch (IllegalStateException e) { Slog.w(TAG, "problem reading network stats: " + e); } catch (RemoteException e) { @@ -830,27 +744,16 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // TODO: consider marking "untrusted" times in historical stats final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis() : System.currentTimeMillis(); - final long threshold = mSettings.getPersistThreshold(); - final NetworkStats uidSnapshot; - final NetworkStats networkXtSnapshot; - final NetworkStats networkDevSnapshot; try { - // collect any tethering stats - final NetworkStats tetherSnapshot = getNetworkStatsTethering(); + // snapshot and record current counters; read UID stats first to + // avoid overcounting dev stats. + final NetworkStats uidSnapshot = getNetworkStatsUidDetail(); + final NetworkStats devSnapshot = getNetworkStatsSummary(); - // record uid stats, folding in tethering stats - uidSnapshot = mNetworkManager.getNetworkStatsUidDetail(UID_ALL); - uidSnapshot.combineAllValues(tetherSnapshot); - performUidPollLocked(uidSnapshot, currentTime); - - // record dev network stats - networkDevSnapshot = mNetworkManager.getNetworkStatsSummary(); - performNetworkDevPollLocked(networkDevSnapshot, currentTime); - - // record xt network stats - networkXtSnapshot = computeNetworkXtSnapshotFromUid(uidSnapshot); - performNetworkXtPollLocked(networkXtSnapshot, currentTime); + mDevRecorder.recordSnapshotLocked(devSnapshot, mActiveIfaces, currentTime); + mUidRecorder.recordSnapshotLocked(uidSnapshot, mActiveIfaces, currentTime); + mUidTagRecorder.recordSnapshotLocked(uidSnapshot, mActiveIfaces, currentTime); } catch (IllegalStateException e) { Log.wtf(TAG, "problem reading network stats", e); @@ -860,26 +763,19 @@ public class NetworkStatsService extends INetworkStatsService.Stub { return; } - // persist when enough network data has occurred - final long persistNetworkDevDelta = computeStatsDelta( - mLastPersistNetworkDevSnapshot, networkDevSnapshot, true, "devp").getTotalBytes(); - final long persistNetworkXtDelta = computeStatsDelta( - mLastPersistNetworkXtSnapshot, networkXtSnapshot, true, "xtp").getTotalBytes(); - final boolean networkOverThreshold = persistNetworkDevDelta > threshold - || persistNetworkXtDelta > threshold; - if (persistForce || (persistNetwork && networkOverThreshold)) { - writeNetworkDevStatsLocked(); - writeNetworkXtStatsLocked(); - mLastPersistNetworkDevSnapshot = networkDevSnapshot; - mLastPersistNetworkXtSnapshot = networkXtSnapshot; - } - - // persist when enough uid data has occurred - final long persistUidDelta = computeStatsDelta( - mLastPersistUidSnapshot, uidSnapshot, true, "uidp").getTotalBytes(); - if (persistForce || (persistUid && persistUidDelta > threshold)) { - writeUidStatsLocked(); - mLastPersistUidSnapshot = uidSnapshot; + // persist any pending data depending on requested flags + if (persistForce) { + mDevRecorder.forcePersistLocked(currentTime); + mUidRecorder.forcePersistLocked(currentTime); + mUidTagRecorder.forcePersistLocked(currentTime); + } else { + if (persistNetwork) { + mDevRecorder.maybePersistLocked(currentTime); + } + if (persistUid) { + mUidRecorder.maybePersistLocked(currentTime); + mUidTagRecorder.maybePersistLocked(currentTime); + } } if (LOGV) { @@ -887,9 +783,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { Slog.v(TAG, "performPollLocked() took " + duration + "ms"); } - if (ENABLE_SAMPLE_AFTER_POLL) { + if (mSettings.getSampleEnabled()) { // sample stats after each full poll - performSample(); + performSampleLocked(); } // finally, dispatch updated event to any listeners @@ -898,512 +794,59 @@ public class NetworkStatsService extends INetworkStatsService.Stub { mContext.sendBroadcast(updatedIntent, READ_NETWORK_USAGE_HISTORY); } - /** - * Update {@link #mNetworkDevStats} historical usage. - */ - private void performNetworkDevPollLocked(NetworkStats networkDevSnapshot, long currentTime) { - final HashSet unknownIface = Sets.newHashSet(); - - final NetworkStats delta = computeStatsDelta( - mLastPollNetworkDevSnapshot, networkDevSnapshot, false, "dev"); - final long timeStart = currentTime - delta.getElapsedRealtime(); - - NetworkStats.Entry entry = null; - for (int i = 0; i < delta.size(); i++) { - entry = delta.getValues(i, entry); - final NetworkIdentitySet ident = mActiveIfaces.get(entry.iface); - if (ident == null) { - unknownIface.add(entry.iface); - continue; - } - - final NetworkStatsHistory history = findOrCreateNetworkDevStatsLocked(ident); - history.recordData(timeStart, currentTime, entry); - } - - mLastPollNetworkDevSnapshot = networkDevSnapshot; - - if (LOGD && unknownIface.size() > 0) { - Slog.w(TAG, "unknown dev interfaces " + unknownIface + ", ignoring those stats"); - } - } - - /** - * Update {@link #mNetworkXtStats} historical usage. - */ - private void performNetworkXtPollLocked(NetworkStats networkXtSnapshot, long currentTime) { - final HashSet unknownIface = Sets.newHashSet(); - - final NetworkStats delta = computeStatsDelta( - mLastPollNetworkXtSnapshot, networkXtSnapshot, false, "xt"); - final long timeStart = currentTime - delta.getElapsedRealtime(); - - NetworkStats.Entry entry = null; - for (int i = 0; i < delta.size(); i++) { - entry = delta.getValues(i, entry); - final NetworkIdentitySet ident = mActiveIfaces.get(entry.iface); - if (ident == null) { - unknownIface.add(entry.iface); - continue; - } - - final NetworkStatsHistory history = findOrCreateNetworkXtStatsLocked(ident); - history.recordData(timeStart, currentTime, entry); - } - - mLastPollNetworkXtSnapshot = networkXtSnapshot; - - if (LOGD && unknownIface.size() > 0) { - Slog.w(TAG, "unknown xt interfaces " + unknownIface + ", ignoring those stats"); - } - } - - /** - * Update {@link #mUidStats} historical usage. - */ - private void performUidPollLocked(NetworkStats uidSnapshot, long currentTime) { - ensureUidStatsLoadedLocked(); - - final NetworkStats delta = computeStatsDelta( - mLastPollUidSnapshot, uidSnapshot, false, "uid"); - final NetworkStats operationsDelta = computeStatsDelta( - mLastPollOperationsSnapshot, mOperations, false, "uidop"); - final long timeStart = currentTime - delta.getElapsedRealtime(); - - NetworkStats.Entry entry = null; - NetworkStats.Entry operationsEntry = null; - for (int i = 0; i < delta.size(); i++) { - entry = delta.getValues(i, entry); - final NetworkIdentitySet ident = mActiveIfaces.get(entry.iface); - if (ident == null) { - if (entry.rxBytes > 0 || entry.rxPackets > 0 || entry.txBytes > 0 - || entry.txPackets > 0) { - Log.w(TAG, "dropping UID delta from unknown iface: " + entry); - } - continue; - } - - // splice in operation counts since last poll - final int j = operationsDelta.findIndex(IFACE_ALL, entry.uid, entry.set, entry.tag); - if (j != -1) { - operationsEntry = operationsDelta.getValues(j, operationsEntry); - entry.operations = operationsEntry.operations; - } - - final NetworkStatsHistory history = findOrCreateUidStatsLocked( - ident, entry.uid, entry.set, entry.tag); - history.recordData(timeStart, currentTime, entry); - } - - mLastPollUidSnapshot = uidSnapshot; - mLastPollOperationsSnapshot = mOperations.clone(); - } - /** * Sample recent statistics summary into {@link EventLog}. */ - private void performSample() { - final long largestBucketSize = Math.max( - mSettings.getNetworkBucketDuration(), mSettings.getUidBucketDuration()); - - // take sample as atomic buckets - final long now = mTime.hasCache() ? mTime.currentTimeMillis() : System.currentTimeMillis(); - final long end = now - (now % largestBucketSize) + largestBucketSize; - final long start = end - largestBucketSize; - + private void performSampleLocked() { + // TODO: migrate trustedtime fixes to separate binary log events final long trustedTime = mTime.hasCache() ? mTime.currentTimeMillis() : -1; - long devHistoryStart = Long.MAX_VALUE; - NetworkTemplate template = null; - NetworkStats.Entry devTotal = null; - NetworkStats.Entry xtTotal = null; - NetworkStats.Entry uidTotal = null; + NetworkTemplate template; + NetworkStats.Entry devTotal; + NetworkStats.Entry xtTotal; + NetworkStats.Entry uidTotal; // collect mobile sample template = buildTemplateMobileAll(getActiveSubscriberId(mContext)); - devTotal = getSummaryForNetworkDev(template, start, end).getTotal(devTotal); - devHistoryStart = getHistoryStartLocked(template, mNetworkDevStats); - xtTotal = getSummaryForNetworkXt(template, start, end).getTotal(xtTotal); - uidTotal = getSummaryForAllUid(template, start, end, false).getTotal(uidTotal); + devTotal = mDevRecorder.getTotalSinceBootLocked(template); + xtTotal = new NetworkStats.Entry(); + uidTotal = mUidRecorder.getTotalSinceBootLocked(template); EventLogTags.writeNetstatsMobileSample( devTotal.rxBytes, devTotal.rxPackets, devTotal.txBytes, devTotal.txPackets, xtTotal.rxBytes, xtTotal.rxPackets, xtTotal.txBytes, xtTotal.txPackets, uidTotal.rxBytes, uidTotal.rxPackets, uidTotal.txBytes, uidTotal.txPackets, - trustedTime, devHistoryStart); + trustedTime); // collect wifi sample template = buildTemplateWifi(); - devTotal = getSummaryForNetworkDev(template, start, end).getTotal(devTotal); - devHistoryStart = getHistoryStartLocked(template, mNetworkDevStats); - xtTotal = getSummaryForNetworkXt(template, start, end).getTotal(xtTotal); - uidTotal = getSummaryForAllUid(template, start, end, false).getTotal(uidTotal); + devTotal = mDevRecorder.getTotalSinceBootLocked(template); + xtTotal = new NetworkStats.Entry(); + uidTotal = mUidRecorder.getTotalSinceBootLocked(template); + EventLogTags.writeNetstatsWifiSample( devTotal.rxBytes, devTotal.rxPackets, devTotal.txBytes, devTotal.txPackets, xtTotal.rxBytes, xtTotal.rxPackets, xtTotal.txBytes, xtTotal.txPackets, uidTotal.rxBytes, uidTotal.rxPackets, uidTotal.txBytes, uidTotal.txPackets, - trustedTime, devHistoryStart); + trustedTime); } /** - * Clean up {@link #mUidStats} after UID is removed. + * Clean up {@link #mUidRecorder} after UID is removed. */ private void removeUidLocked(int uid) { - ensureUidStatsLoadedLocked(); - // perform one last poll before removing performPollLocked(FLAG_PERSIST_ALL); - final ArrayList knownKeys = Lists.newArrayList(); - knownKeys.addAll(mUidStats.keySet()); - - // migrate all UID stats into special "removed" bucket - for (UidStatsKey key : knownKeys) { - if (key.uid == uid) { - // only migrate combined TAG_NONE history - if (key.tag == TAG_NONE) { - final NetworkStatsHistory uidHistory = mUidStats.get(key); - final NetworkStatsHistory removedHistory = findOrCreateUidStatsLocked( - key.ident, UID_REMOVED, SET_DEFAULT, TAG_NONE); - removedHistory.recordEntireHistory(uidHistory); - } - mUidStats.remove(key); - } - } - - // clear UID from current stats snapshot - if (mLastPollUidSnapshot != null) { - mLastPollUidSnapshot = mLastPollUidSnapshot.withoutUid(uid); - mLastPollNetworkXtSnapshot = computeNetworkXtSnapshotFromUid(mLastPollUidSnapshot); - } + mUidRecorder.removeUidLocked(uid); + mUidTagRecorder.removeUidLocked(uid); // clear kernel stats associated with UID resetKernelUidStats(uid); - - // since this was radical rewrite, push to disk - writeUidStatsLocked(); - } - - private NetworkStatsHistory findOrCreateNetworkXtStatsLocked(NetworkIdentitySet ident) { - return findOrCreateNetworkStatsLocked(ident, mNetworkXtStats); - } - - private NetworkStatsHistory findOrCreateNetworkDevStatsLocked(NetworkIdentitySet ident) { - return findOrCreateNetworkStatsLocked(ident, mNetworkDevStats); - } - - private NetworkStatsHistory findOrCreateNetworkStatsLocked( - NetworkIdentitySet ident, HashMap source) { - final NetworkStatsHistory existing = source.get(ident); - - // update when no existing, or when bucket duration changed - final long bucketDuration = mSettings.getNetworkBucketDuration(); - NetworkStatsHistory updated = null; - if (existing == null) { - updated = new NetworkStatsHistory(bucketDuration, 10); - } else if (existing.getBucketDuration() != bucketDuration) { - updated = new NetworkStatsHistory( - bucketDuration, estimateResizeBuckets(existing, bucketDuration)); - updated.recordEntireHistory(existing); - } - - if (updated != null) { - source.put(ident, updated); - return updated; - } else { - return existing; - } - } - - private NetworkStatsHistory findOrCreateUidStatsLocked( - NetworkIdentitySet ident, int uid, int set, int tag) { - ensureUidStatsLoadedLocked(); - - final UidStatsKey key = new UidStatsKey(ident, uid, set, tag); - final NetworkStatsHistory existing = mUidStats.get(key); - - // update when no existing, or when bucket duration changed - final long bucketDuration = mSettings.getUidBucketDuration(); - NetworkStatsHistory updated = null; - if (existing == null) { - updated = new NetworkStatsHistory(bucketDuration, 10); - } else if (existing.getBucketDuration() != bucketDuration) { - updated = new NetworkStatsHistory( - bucketDuration, estimateResizeBuckets(existing, bucketDuration)); - updated.recordEntireHistory(existing); - } - - if (updated != null) { - mUidStats.put(key, updated); - return updated; - } else { - return existing; - } - } - - private void readNetworkDevStatsLocked() { - if (LOGV) Slog.v(TAG, "readNetworkDevStatsLocked()"); - readNetworkStats(mNetworkDevFile, mNetworkDevStats); - } - - private void readNetworkXtStatsLocked() { - if (LOGV) Slog.v(TAG, "readNetworkXtStatsLocked()"); - readNetworkStats(mNetworkXtFile, mNetworkXtStats); - } - - private static void readNetworkStats( - AtomicFile inputFile, HashMap output) { - // clear any existing stats and read from disk - output.clear(); - - 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); - output.put(ident, history); - } - break; - } - default: { - throw new ProtocolException("unexpected version: " + version); - } - } - } catch (FileNotFoundException e) { - // missing stats is okay, probably first boot - } catch (IOException e) { - Log.wtf(TAG, "problem reading network stats", e); - } finally { - IoUtils.closeQuietly(in); - } - } - - private void ensureUidStatsLoadedLocked() { - if (!mUidStatsLoaded) { - readUidStatsLocked(); - mUidStatsLoaded = true; - } - } - - private void readUidStatsLocked() { - if (LOGV) Slog.v(TAG, "readUidStatsLocked()"); - - // clear any existing stats and read from disk - mUidStats.clear(); - - DataInputStream in = null; - try { - in = new DataInputStream(new BufferedInputStream(mUidFile.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 UidStatsKey key = new UidStatsKey(ident, uid, set, tag); - final NetworkStatsHistory history = new NetworkStatsHistory(in); - mUidStats.put(key, history); - } - } - break; - } - default: { - throw new ProtocolException("unexpected version: " + version); - } - } - } catch (FileNotFoundException e) { - // missing stats is okay, probably first boot - } catch (IOException e) { - Log.wtf(TAG, "problem reading uid stats", e); - } finally { - IoUtils.closeQuietly(in); - } - } - - private void writeNetworkDevStatsLocked() { - if (LOGV) Slog.v(TAG, "writeNetworkDevStatsLocked()"); - writeNetworkStats(mNetworkDevStats, mNetworkDevFile); - } - - private void writeNetworkXtStatsLocked() { - if (LOGV) Slog.v(TAG, "writeNetworkXtStatsLocked()"); - writeNetworkStats(mNetworkXtStats, mNetworkXtFile); - } - - private void writeNetworkStats( - HashMap input, AtomicFile outputFile) { - // TODO: consider duplicating stats and releasing lock while writing - - // trim any history beyond max - if (mTime.hasCache()) { - final long systemCurrentTime = System.currentTimeMillis(); - final long trustedCurrentTime = mTime.currentTimeMillis(); - - final long currentTime = Math.min(systemCurrentTime, trustedCurrentTime); - final long maxHistory = mSettings.getNetworkMaxHistory(); - - for (NetworkStatsHistory history : input.values()) { - final int beforeSize = history.size(); - history.removeBucketsBefore(currentTime - maxHistory); - final int afterSize = history.size(); - - if (beforeSize > 24 && afterSize < beforeSize / 2) { - // yikes, dropping more than half of significant history - final StringBuilder builder = new StringBuilder(); - builder.append("yikes, dropping more than half of history").append('\n'); - builder.append("systemCurrentTime=").append(systemCurrentTime).append('\n'); - builder.append("trustedCurrentTime=").append(trustedCurrentTime).append('\n'); - builder.append("maxHistory=").append(maxHistory).append('\n'); - builder.append("beforeSize=").append(beforeSize).append('\n'); - builder.append("afterSize=").append(afterSize).append('\n'); - mDropBox.addText(TAG_NETSTATS_ERROR, builder.toString()); - } - } - } - - FileOutputStream fos = null; - try { - fos = outputFile.startWrite(); - final DataOutputStream out = new DataOutputStream(new BufferedOutputStream(fos)); - - out.writeInt(FILE_MAGIC); - out.writeInt(VERSION_NETWORK_INIT); - - out.writeInt(input.size()); - for (NetworkIdentitySet ident : input.keySet()) { - final NetworkStatsHistory history = input.get(ident); - ident.writeToStream(out); - history.writeToStream(out); - } - - out.flush(); - outputFile.finishWrite(fos); - } catch (IOException e) { - Log.wtf(TAG, "problem writing stats", e); - if (fos != null) { - outputFile.failWrite(fos); - } - } - } - - private void writeUidStatsLocked() { - if (LOGV) Slog.v(TAG, "writeUidStatsLocked()"); - - if (!mUidStatsLoaded) { - Slog.w(TAG, "asked to write UID stats when not loaded; skipping"); - return; - } - - // TODO: consider duplicating stats and releasing lock while writing - - // trim any history beyond max - if (mTime.hasCache()) { - final long currentTime = Math.min( - System.currentTimeMillis(), mTime.currentTimeMillis()); - final long maxUidHistory = mSettings.getUidMaxHistory(); - final long maxTagHistory = mSettings.getTagMaxHistory(); - for (UidStatsKey key : mUidStats.keySet()) { - final NetworkStatsHistory history = mUidStats.get(key); - - // detailed tags are trimmed sooner than summary in TAG_NONE - if (key.tag == TAG_NONE) { - history.removeBucketsBefore(currentTime - maxUidHistory); - } else { - history.removeBucketsBefore(currentTime - maxTagHistory); - } - } - } - - // build UidStatsKey lists grouped by ident - final HashMap> keysByIdent = Maps.newHashMap(); - for (UidStatsKey key : mUidStats.keySet()) { - ArrayList keys = keysByIdent.get(key.ident); - if (keys == null) { - keys = Lists.newArrayList(); - keysByIdent.put(key.ident, keys); - } - keys.add(key); - } - - FileOutputStream fos = null; - try { - fos = mUidFile.startWrite(); - final DataOutputStream out = new DataOutputStream(new BufferedOutputStream(fos)); - - out.writeInt(FILE_MAGIC); - out.writeInt(VERSION_UID_WITH_SET); - - out.writeInt(keysByIdent.size()); - for (NetworkIdentitySet ident : keysByIdent.keySet()) { - final ArrayList keys = keysByIdent.get(ident); - ident.writeToStream(out); - - out.writeInt(keys.size()); - for (UidStatsKey key : keys) { - final NetworkStatsHistory history = mUidStats.get(key); - out.writeInt(key.uid); - out.writeInt(key.set); - out.writeInt(key.tag); - history.writeToStream(out); - } - } - - out.flush(); - mUidFile.finishWrite(fos); - } catch (IOException e) { - Log.wtf(TAG, "problem writing stats", e); - if (fos != null) { - mUidFile.failWrite(fos); - } - } } @Override - protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { mContext.enforceCallingOrSelfPermission(DUMP, TAG); final HashSet argSet = new HashSet(); @@ -1411,187 +854,68 @@ public class NetworkStatsService extends INetworkStatsService.Stub { argSet.add(arg); } - final boolean fullHistory = argSet.contains("full"); + // usage: dumpsys netstats --full --uid --tag + final boolean poll = argSet.contains("--poll") || argSet.contains("poll"); + final boolean fullHistory = argSet.contains("--full") || argSet.contains("full"); + final boolean includeUid = argSet.contains("--uid") || argSet.contains("detail"); + final boolean includeTag = argSet.contains("--tag") || argSet.contains("detail"); + + final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); synchronized (mStatsLock) { - // TODO: remove this testing code, since it corrupts stats - if (argSet.contains("generate")) { - generateRandomLocked(args); - pw.println("Generated stub stats"); - return; - } - - if (argSet.contains("poll")) { + if (poll) { performPollLocked(FLAG_PERSIST_ALL | FLAG_PERSIST_FORCE); pw.println("Forced poll"); return; } pw.println("Active interfaces:"); + pw.increaseIndent(); for (String iface : mActiveIfaces.keySet()) { final NetworkIdentitySet ident = mActiveIfaces.get(iface); - pw.print(" iface="); pw.print(iface); + pw.print("iface="); pw.print(iface); pw.print(" ident="); pw.println(ident.toString()); } + pw.decreaseIndent(); - pw.println("Known historical dev stats:"); - for (NetworkIdentitySet ident : mNetworkDevStats.keySet()) { - final NetworkStatsHistory history = mNetworkDevStats.get(ident); - pw.print(" ident="); pw.println(ident.toString()); - history.dump(" ", pw, fullHistory); + pw.println("Dev stats:"); + pw.increaseIndent(); + mDevRecorder.dumpLocked(pw, fullHistory); + pw.decreaseIndent(); + + if (includeUid) { + pw.println("UID stats:"); + pw.increaseIndent(); + mUidRecorder.dumpLocked(pw, fullHistory); + pw.decreaseIndent(); } - pw.println("Known historical xt stats:"); - for (NetworkIdentitySet ident : mNetworkXtStats.keySet()) { - final NetworkStatsHistory history = mNetworkXtStats.get(ident); - pw.print(" ident="); pw.println(ident.toString()); - history.dump(" ", pw, fullHistory); - } - - if (argSet.contains("detail")) { - // since explicitly requested with argument, we're okay to load - // from disk if not already in memory. - ensureUidStatsLoadedLocked(); - - final ArrayList keys = Lists.newArrayList(); - keys.addAll(mUidStats.keySet()); - Collections.sort(keys); - - pw.println("Detailed UID stats:"); - for (UidStatsKey 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 = mUidStats.get(key); - history.dump(" ", pw, fullHistory); - } + if (includeTag) { + pw.println("UID tag stats:"); + pw.increaseIndent(); + mUidTagRecorder.dumpLocked(pw, fullHistory); + pw.decreaseIndent(); } } } + private NetworkStats getNetworkStatsSummary() throws RemoteException { + return mNetworkManager.getNetworkStatsSummary(); + } + /** - * @deprecated only for temporary testing + * Return snapshot of current UID statistics, including any + * {@link TrafficStats#UID_TETHERING} and {@link #mUidOperations} values. */ - @Deprecated - private void generateRandomLocked(String[] args) { - final long totalBytes = Long.parseLong(args[1]); - final long totalTime = Long.parseLong(args[2]); - - final PackageManager pm = mContext.getPackageManager(); - final ArrayList specialUidList = Lists.newArrayList(); - for (int i = 3; i < args.length; i++) { - try { - specialUidList.add(pm.getApplicationInfo(args[i], 0).uid); - } catch (NameNotFoundException e) { - throw new RuntimeException(e); - } - } + private NetworkStats getNetworkStatsUidDetail() throws RemoteException { + final NetworkStats uidSnapshot = mNetworkManager.getNetworkStatsUidDetail(UID_ALL); - final HashSet otherUidSet = Sets.newHashSet(); - for (ApplicationInfo info : pm.getInstalledApplications(0)) { - if (pm.checkPermission(android.Manifest.permission.INTERNET, info.packageName) - == PackageManager.PERMISSION_GRANTED && !specialUidList.contains(info.uid)) { - otherUidSet.add(info.uid); - } - } + // fold tethering stats and operations into uid snapshot + final NetworkStats tetherSnapshot = getNetworkStatsTethering(); + uidSnapshot.combineAllValues(tetherSnapshot); + uidSnapshot.combineAllValues(mUidOperations); - final ArrayList otherUidList = new ArrayList(otherUidSet); - - final long end = System.currentTimeMillis(); - final long start = end - totalTime; - - mNetworkDevStats.clear(); - mNetworkXtStats.clear(); - mUidStats.clear(); - - final Random r = new Random(); - for (NetworkIdentitySet ident : mActiveIfaces.values()) { - final NetworkStatsHistory devHistory = findOrCreateNetworkDevStatsLocked(ident); - final NetworkStatsHistory xtHistory = findOrCreateNetworkXtStatsLocked(ident); - - final ArrayList uidList = new ArrayList(); - uidList.addAll(specialUidList); - - if (uidList.size() == 0) { - Collections.shuffle(otherUidList); - uidList.addAll(otherUidList); - } - - boolean first = true; - long remainingBytes = totalBytes; - for (int uid : uidList) { - final NetworkStatsHistory defaultHistory = findOrCreateUidStatsLocked( - ident, uid, SET_DEFAULT, TAG_NONE); - final NetworkStatsHistory foregroundHistory = findOrCreateUidStatsLocked( - ident, uid, SET_FOREGROUND, TAG_NONE); - - final long uidBytes = totalBytes / uidList.size(); - - final float fractionDefault = r.nextFloat(); - final long defaultBytes = (long) (uidBytes * fractionDefault); - final long foregroundBytes = (long) (uidBytes * (1 - fractionDefault)); - - defaultHistory.generateRandom(start, end, defaultBytes); - foregroundHistory.generateRandom(start, end, foregroundBytes); - - if (first) { - final long bumpTime = (start + end) / 2; - defaultHistory.recordData( - bumpTime, bumpTime + DAY_IN_MILLIS, 200 * MB_IN_BYTES, 0); - first = false; - } - - devHistory.recordEntireHistory(defaultHistory); - devHistory.recordEntireHistory(foregroundHistory); - xtHistory.recordEntireHistory(defaultHistory); - xtHistory.recordEntireHistory(foregroundHistory); - } - } - } - - private StatsObserver mStatsObserver = new StatsObserver(); - - private class StatsObserver implements NonMonotonicObserver { - private String mCurrentType; - - public void setCurrentType(String type) { - mCurrentType = type; - } - - /** {@inheritDoc} */ - public void foundNonMonotonic( - NetworkStats left, int leftIndex, NetworkStats right, int rightIndex) { - Log.w(TAG, "found non-monotonic values; saving to dropbox"); - - // record error for debugging - final StringBuilder builder = new StringBuilder(); - builder.append("found non-monotonic " + mCurrentType + " values at left[" + leftIndex - + "] - right[" + rightIndex + "]\n"); - builder.append("left=").append(left).append('\n'); - builder.append("right=").append(right).append('\n'); - mDropBox.addText(TAG_NETSTATS_ERROR, builder.toString()); - } - } - - /** - * Return the delta between two {@link NetworkStats} snapshots, where {@code - * before} can be {@code null}. - */ - private NetworkStats computeStatsDelta( - NetworkStats before, NetworkStats current, boolean collectStale, String type) { - if (before != null) { - mStatsObserver.setCurrentType(type); - return NetworkStats.subtract(current, before, mStatsObserver); - } else if (collectStale) { - // caller is okay collecting stale stats for first call. - return current; - } else { - // this is first snapshot; to prevent from double-counting we only - // observe traffic occuring between known snapshots. - return new NetworkStats(0L, 10); - } + return uidSnapshot; } /** @@ -1608,35 +932,6 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } } - private static NetworkStats computeNetworkXtSnapshotFromUid(NetworkStats uidSnapshot) { - return uidSnapshot.groupedByIface(); - } - - private int estimateNetworkBuckets() { - return (int) (mSettings.getNetworkMaxHistory() / mSettings.getNetworkBucketDuration()); - } - - private int estimateUidBuckets() { - return (int) (mSettings.getUidMaxHistory() / mSettings.getUidBucketDuration()); - } - - private static int estimateResizeBuckets(NetworkStatsHistory existing, long newBucketDuration) { - return (int) (existing.size() * existing.getBucketDuration() / newBucketDuration); - } - - /** - * 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 Handler.Callback mHandlerCallback = new Handler.Callback() { /** {@inheritDoc} */ public boolean handleMessage(Message msg) { @@ -1672,40 +967,22 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } } - /** - * Key uniquely identifying a {@link NetworkStatsHistory} for a UID. - */ - private static class UidStatsKey implements Comparable { - public final NetworkIdentitySet ident; - public final int uid; - public final int set; - public final int tag; - - public UidStatsKey(NetworkIdentitySet ident, int uid, int set, int tag) { - this.ident = ident; - this.uid = uid; - this.set = set; - this.tag = tag; - } - - @Override - public int hashCode() { - return Objects.hashCode(ident, uid, set, tag); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof UidStatsKey) { - final UidStatsKey key = (UidStatsKey) obj; - return Objects.equal(ident, key.ident) && uid == key.uid && set == key.set - && tag == key.tag; - } - return false; - } - + private class DropBoxNonMonotonicObserver implements NonMonotonicObserver { /** {@inheritDoc} */ - public int compareTo(UidStatsKey another) { - return Integer.compare(uid, another.uid); + public void foundNonMonotonic(NetworkStats left, int leftIndex, NetworkStats right, + int rightIndex, String cookie) { + Log.w(TAG, "found non-monotonic values; saving to dropbox"); + + // record error for debugging + final StringBuilder builder = new StringBuilder(); + builder.append("found non-monotonic " + cookie + " values at left[" + leftIndex + + "] - right[" + rightIndex + "]\n"); + builder.append("left=").append(left).append('\n'); + builder.append("right=").append(right).append('\n'); + + final DropBoxManager dropBox = (DropBoxManager) mContext.getSystemService( + Context.DROPBOX_SERVICE); + dropBox.addText(TAG_NETSTATS_ERROR, builder.toString()); } } @@ -1731,26 +1008,35 @@ public class NetworkStatsService extends INetworkStatsService.Stub { public long getPollInterval() { return getSecureLong(NETSTATS_POLL_INTERVAL, 30 * MINUTE_IN_MILLIS); } - public long getPersistThreshold() { - return getSecureLong(NETSTATS_PERSIST_THRESHOLD, 2 * MB_IN_BYTES); - } - public long getNetworkBucketDuration() { - return getSecureLong(NETSTATS_NETWORK_BUCKET_DURATION, HOUR_IN_MILLIS); - } - public long getNetworkMaxHistory() { - return getSecureLong(NETSTATS_NETWORK_MAX_HISTORY, 90 * DAY_IN_MILLIS); - } - public long getUidBucketDuration() { - return getSecureLong(NETSTATS_UID_BUCKET_DURATION, 2 * HOUR_IN_MILLIS); - } - public long getUidMaxHistory() { - return getSecureLong(NETSTATS_UID_MAX_HISTORY, 90 * DAY_IN_MILLIS); - } - public long getTagMaxHistory() { - return getSecureLong(NETSTATS_TAG_MAX_HISTORY, 30 * DAY_IN_MILLIS); - } public long getTimeCacheMaxAge() { - return DAY_IN_MILLIS; + return getSecureLong(NETSTATS_TIME_CACHE_MAX_AGE, DAY_IN_MILLIS); + } + public long getGlobalAlertBytes() { + return getSecureLong(NETSTATS_GLOBAL_ALERT_BYTES, 2 * MB_IN_BYTES); + } + public boolean getSampleEnabled() { + return getSecureBoolean(NETSTATS_SAMPLE_ENABLED, true); + } + + public Config getDevConfig() { + return new Config(getSecureLong(NETSTATS_DEV_BUCKET_DURATION, HOUR_IN_MILLIS), + getSecureLong(NETSTATS_DEV_PERSIST_BYTES, 2 * MB_IN_BYTES), + getSecureLong(NETSTATS_DEV_ROTATE_AGE, 15 * DAY_IN_MILLIS), + getSecureLong(NETSTATS_DEV_DELETE_AGE, 90 * DAY_IN_MILLIS)); + } + + public Config getUidConfig() { + return new Config(getSecureLong(NETSTATS_UID_BUCKET_DURATION, 2 * HOUR_IN_MILLIS), + getSecureLong(NETSTATS_UID_PERSIST_BYTES, 2 * MB_IN_BYTES), + getSecureLong(NETSTATS_UID_ROTATE_AGE, 15 * DAY_IN_MILLIS), + getSecureLong(NETSTATS_UID_DELETE_AGE, 90 * DAY_IN_MILLIS)); + } + + public Config getUidTagConfig() { + return new Config(getSecureLong(NETSTATS_UID_BUCKET_DURATION, 2 * HOUR_IN_MILLIS), + getSecureLong(NETSTATS_UID_PERSIST_BYTES, 2 * MB_IN_BYTES), + getSecureLong(NETSTATS_UID_ROTATE_AGE, 5 * DAY_IN_MILLIS), + getSecureLong(NETSTATS_UID_DELETE_AGE, 15 * DAY_IN_MILLIS)); } } }