Add operation counts to network statistics.

Provide API to increment "operation counts" for a UID and tag, used
to eventually derive bytes/operation stats.  Internally is stored at
network layer, but should belong at data layer.  Switch profiling
to use data layer stats, which are emulated by summarizing network
layer details.

Read packet counts from new /proc/ columns and collect them into
NetworkStatsHistory.  Prevent double-counting by ignoring values from
first snapshot.  Watch for duplicate /proc/ entries.  Update tests
to verify packet and operation counters.

Bug: 5052136, 5097392
Change-Id: I1832f65a2b8a9188f8088f253474a667c21a2f09
This commit is contained in:
Jeff Sharkey
2011-07-19 23:47:12 -07:00
parent 0c6ebed1df
commit 7407e09009
5 changed files with 340 additions and 102 deletions

View File

@@ -23,16 +23,21 @@ import android.net.NetworkTemplate;
/** {@hide} */ /** {@hide} */
interface INetworkStatsService { interface INetworkStatsService {
/** Return historical stats for traffic that matches template. */ /** Return historical network layer stats for traffic that matches template. */
NetworkStatsHistory getHistoryForNetwork(in NetworkTemplate template); NetworkStatsHistory getHistoryForNetwork(in NetworkTemplate template);
/** Return historical stats for specific UID traffic that matches template. */ /** Return historical network layer stats for specific UID traffic that matches template. */
NetworkStatsHistory getHistoryForUid(in NetworkTemplate template, int uid, int tag); NetworkStatsHistory getHistoryForUid(in NetworkTemplate template, int uid, int tag);
/** Return usage summary for traffic that matches template. */ /** Return network layer usage summary for traffic that matches template. */
NetworkStats getSummaryForNetwork(in NetworkTemplate template, long start, long end); NetworkStats getSummaryForNetwork(in NetworkTemplate template, long start, long end);
/** Return usage summary per UID for traffic that matches template. */ /** Return network layer usage summary per UID for traffic that matches template. */
NetworkStats getSummaryForAllUid(in NetworkTemplate template, long start, long end, boolean includeTags); NetworkStats getSummaryForAllUid(in NetworkTemplate template, long start, long end, boolean includeTags);
/** Return data layer snapshot of UID network usage. */
NetworkStats getDataLayerSnapshotForUid(int uid);
/** Increment data layer count of operations performed for UID and tag. */
void incrementOperationCount(int uid, int tag, int operationCount);
/** Force update of statistics. */ /** Force update of statistics. */
void forceUpdate(); void forceUpdate();

View File

@@ -21,6 +21,8 @@ import android.os.Parcelable;
import android.os.SystemClock; import android.os.SystemClock;
import android.util.SparseBooleanArray; import android.util.SparseBooleanArray;
import com.android.internal.util.Objects;
import java.io.CharArrayWriter; import java.io.CharArrayWriter;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.Arrays; import java.util.Arrays;
@@ -56,6 +58,7 @@ public class NetworkStats implements Parcelable {
private long[] rxPackets; private long[] rxPackets;
private long[] txBytes; private long[] txBytes;
private long[] txPackets; private long[] txPackets;
private int[] operations;
public static class Entry { public static class Entry {
public String iface; public String iface;
@@ -65,12 +68,13 @@ public class NetworkStats implements Parcelable {
public long rxPackets; public long rxPackets;
public long txBytes; public long txBytes;
public long txPackets; public long txPackets;
public int operations;
public Entry() { public Entry() {
} }
public Entry(String iface, int uid, int tag, long rxBytes, long rxPackets, long txBytes, public Entry(String iface, int uid, int tag, long rxBytes, long rxPackets, long txBytes,
long txPackets) { long txPackets, int operations) {
this.iface = iface; this.iface = iface;
this.uid = uid; this.uid = uid;
this.tag = tag; this.tag = tag;
@@ -78,6 +82,7 @@ public class NetworkStats implements Parcelable {
this.rxPackets = rxPackets; this.rxPackets = rxPackets;
this.txBytes = txBytes; this.txBytes = txBytes;
this.txPackets = txPackets; this.txPackets = txPackets;
this.operations = operations;
} }
} }
@@ -91,6 +96,7 @@ public class NetworkStats implements Parcelable {
this.rxPackets = new long[initialSize]; this.rxPackets = new long[initialSize];
this.txBytes = new long[initialSize]; this.txBytes = new long[initialSize];
this.txPackets = new long[initialSize]; this.txPackets = new long[initialSize];
this.operations = new int[initialSize];
} }
public NetworkStats(Parcel parcel) { public NetworkStats(Parcel parcel) {
@@ -103,11 +109,32 @@ public class NetworkStats implements Parcelable {
rxPackets = parcel.createLongArray(); rxPackets = parcel.createLongArray();
txBytes = parcel.createLongArray(); txBytes = parcel.createLongArray();
txPackets = parcel.createLongArray(); txPackets = parcel.createLongArray();
operations = parcel.createIntArray();
}
/** {@inheritDoc} */
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(elapsedRealtime);
dest.writeInt(size);
dest.writeStringArray(iface);
dest.writeIntArray(uid);
dest.writeIntArray(tag);
dest.writeLongArray(rxBytes);
dest.writeLongArray(rxPackets);
dest.writeLongArray(txBytes);
dest.writeLongArray(txPackets);
dest.writeIntArray(operations);
} }
public NetworkStats addValues(String iface, int uid, int tag, long rxBytes, long rxPackets, public NetworkStats addValues(String iface, int uid, int tag, long rxBytes, long rxPackets,
long txBytes, long txPackets) { long txBytes, long txPackets) {
return addValues(new Entry(iface, uid, tag, rxBytes, rxPackets, txBytes, txPackets)); return addValues(iface, uid, tag, rxBytes, rxPackets, txBytes, txPackets, 0);
}
public NetworkStats addValues(String iface, int uid, int tag, long rxBytes, long rxPackets,
long txBytes, long txPackets, int operations) {
return addValues(
new Entry(iface, uid, tag, rxBytes, rxPackets, txBytes, txPackets, operations));
} }
/** /**
@@ -124,6 +151,7 @@ public class NetworkStats implements Parcelable {
rxPackets = Arrays.copyOf(rxPackets, newLength); rxPackets = Arrays.copyOf(rxPackets, newLength);
txBytes = Arrays.copyOf(txBytes, newLength); txBytes = Arrays.copyOf(txBytes, newLength);
txPackets = Arrays.copyOf(txPackets, newLength); txPackets = Arrays.copyOf(txPackets, newLength);
operations = Arrays.copyOf(operations, newLength);
} }
iface[size] = entry.iface; iface[size] = entry.iface;
@@ -133,6 +161,7 @@ public class NetworkStats implements Parcelable {
rxPackets[size] = entry.rxPackets; rxPackets[size] = entry.rxPackets;
txBytes[size] = entry.txBytes; txBytes[size] = entry.txBytes;
txPackets[size] = entry.txPackets; txPackets[size] = entry.txPackets;
operations[size] = entry.operations;
size++; size++;
return this; return this;
@@ -150,6 +179,7 @@ public class NetworkStats implements Parcelable {
entry.rxPackets = rxPackets[i]; entry.rxPackets = rxPackets[i];
entry.txBytes = txBytes[i]; entry.txBytes = txBytes[i];
entry.txPackets = txPackets[i]; entry.txPackets = txPackets[i];
entry.operations = operations[i];
return entry; return entry;
} }
@@ -167,8 +197,9 @@ public class NetworkStats implements Parcelable {
} }
public NetworkStats combineValues(String iface, int uid, int tag, long rxBytes, long rxPackets, public NetworkStats combineValues(String iface, int uid, int tag, long rxBytes, long rxPackets,
long txBytes, long txPackets) { long txBytes, long txPackets, int operations) {
return combineValues(new Entry(iface, uid, tag, rxBytes, rxPackets, txBytes, txPackets)); return combineValues(
new Entry(iface, uid, tag, rxBytes, rxPackets, txBytes, txPackets, operations));
} }
/** /**
@@ -186,6 +217,7 @@ public class NetworkStats implements Parcelable {
rxPackets[i] += entry.rxPackets; rxPackets[i] += entry.rxPackets;
txBytes[i] += entry.txBytes; txBytes[i] += entry.txBytes;
txPackets[i] += entry.txPackets; txPackets[i] += entry.txPackets;
operations[i] += entry.operations;
} }
return this; return this;
} }
@@ -195,13 +227,29 @@ public class NetworkStats implements Parcelable {
*/ */
public int findIndex(String iface, int uid, int tag) { public int findIndex(String iface, int uid, int tag) {
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
if (equal(iface, this.iface[i]) && uid == this.uid[i] && tag == this.tag[i]) { if (Objects.equal(iface, this.iface[i]) && uid == this.uid[i] && tag == this.tag[i]) {
return i; return i;
} }
} }
return -1; return -1;
} }
/**
* Splice in {@link #operations} from the given {@link NetworkStats} based
* on matching {@link #uid} and {@link #tag} rows. Ignores {@link #iface},
* since operation counts are at data layer.
*/
public void spliceOperationsFrom(NetworkStats stats) {
for (int i = 0; i < size; i++) {
final int j = stats.findIndex(IFACE_ALL, uid[i], tag[i]);
if (j == -1) {
operations[i] = 0;
} else {
operations[i] = stats.operations[j];
}
}
}
/** /**
* Return list of unique interfaces known by this data structure. * Return list of unique interfaces known by this data structure.
*/ */
@@ -289,15 +337,17 @@ public class NetworkStats implements Parcelable {
entry.rxPackets = rxPackets[i]; entry.rxPackets = rxPackets[i];
entry.txBytes = txBytes[i]; entry.txBytes = txBytes[i];
entry.txPackets = txPackets[i]; entry.txPackets = txPackets[i];
entry.operations = operations[i];
} else { } else {
// existing row, subtract remote value // existing row, subtract remote value
entry.rxBytes = rxBytes[i] - value.rxBytes[j]; entry.rxBytes = rxBytes[i] - value.rxBytes[j];
entry.rxPackets = rxPackets[i] - value.rxPackets[j]; entry.rxPackets = rxPackets[i] - value.rxPackets[j];
entry.txBytes = txBytes[i] - value.txBytes[j]; entry.txBytes = txBytes[i] - value.txBytes[j];
entry.txPackets = txPackets[i] - value.txPackets[j]; entry.txPackets = txPackets[i] - value.txPackets[j];
entry.operations = operations[i] - value.operations[j];
if (enforceMonotonic if (enforceMonotonic
&& (entry.rxBytes < 0 || entry.rxPackets < 0 || entry.txBytes < 0 && (entry.rxBytes < 0 || entry.rxPackets < 0 || entry.txBytes < 0
|| entry.txPackets < 0)) { || entry.txPackets < 0 || entry.operations < 0)) {
throw new IllegalArgumentException("found non-monotonic values"); throw new IllegalArgumentException("found non-monotonic values");
} }
if (clampNegative) { if (clampNegative) {
@@ -305,6 +355,7 @@ public class NetworkStats implements Parcelable {
entry.rxPackets = Math.max(0, entry.rxPackets); entry.rxPackets = Math.max(0, entry.rxPackets);
entry.txBytes = Math.max(0, entry.txBytes); entry.txBytes = Math.max(0, entry.txBytes);
entry.txPackets = Math.max(0, entry.txPackets); entry.txPackets = Math.max(0, entry.txPackets);
entry.operations = Math.max(0, entry.operations);
} }
} }
@@ -314,10 +365,6 @@ public class NetworkStats implements Parcelable {
return result; return result;
} }
private static boolean equal(Object a, Object b) {
return a == b || (a != null && a.equals(b));
}
public void dump(String prefix, PrintWriter pw) { public void dump(String prefix, PrintWriter pw) {
pw.print(prefix); pw.print(prefix);
pw.print("NetworkStats: elapsedRealtime="); pw.println(elapsedRealtime); pw.print("NetworkStats: elapsedRealtime="); pw.println(elapsedRealtime);
@@ -325,11 +372,12 @@ public class NetworkStats implements Parcelable {
pw.print(prefix); pw.print(prefix);
pw.print(" iface="); pw.print(iface[i]); pw.print(" iface="); pw.print(iface[i]);
pw.print(" uid="); pw.print(uid[i]); pw.print(" uid="); pw.print(uid[i]);
pw.print(" tag="); pw.print(tag[i]); pw.print(" tag=0x"); pw.print(Integer.toHexString(tag[i]));
pw.print(" rxBytes="); pw.print(rxBytes[i]); pw.print(" rxBytes="); pw.print(rxBytes[i]);
pw.print(" rxPackets="); pw.print(rxPackets[i]); pw.print(" rxPackets="); pw.print(rxPackets[i]);
pw.print(" txBytes="); pw.print(txBytes[i]); pw.print(" txBytes="); pw.print(txBytes[i]);
pw.print(" txPackets="); pw.println(txPackets[i]); pw.print(" txPackets="); pw.print(txPackets[i]);
pw.print(" operations="); pw.println(operations[i]);
} }
} }
@@ -345,19 +393,6 @@ public class NetworkStats implements Parcelable {
return 0; return 0;
} }
/** {@inheritDoc} */
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(elapsedRealtime);
dest.writeInt(size);
dest.writeStringArray(iface);
dest.writeIntArray(uid);
dest.writeIntArray(tag);
dest.writeLongArray(rxBytes);
dest.writeLongArray(rxPackets);
dest.writeLongArray(txBytes);
dest.writeLongArray(txPackets);
}
public static final Creator<NetworkStats> CREATOR = new Creator<NetworkStats>() { public static final Creator<NetworkStats> CREATOR = new Creator<NetworkStats>() {
public NetworkStats createFromParcel(Parcel in) { public NetworkStats createFromParcel(Parcel in) {
return new NetworkStats(in); return new NetworkStats(in);

View File

@@ -16,6 +16,16 @@
package android.net; package android.net;
import static android.net.NetworkStats.IFACE_ALL;
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL;
import static android.net.NetworkStatsHistory.DataStreamUtils.readLongArray;
import static android.net.NetworkStatsHistory.DataStreamUtils.writeLongArray;
import static android.net.NetworkStatsHistory.ParcelUtils.readIntArray;
import static android.net.NetworkStatsHistory.ParcelUtils.readLongArray;
import static android.net.NetworkStatsHistory.ParcelUtils.writeIntArray;
import static android.net.NetworkStatsHistory.ParcelUtils.writeLongArray;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
@@ -43,19 +53,26 @@ public class NetworkStatsHistory implements Parcelable {
private static final int VERSION_INIT = 1; private static final int VERSION_INIT = 1;
// TODO: teach about varint encoding to use less disk space // TODO: teach about varint encoding to use less disk space
// TODO: extend to record rxPackets/txPackets // TODO: teach about omitting entire fields to reduce parcel pressure
// TODO: persist/restore packet and operation counts
private final long bucketDuration; private final long bucketDuration;
private int bucketCount; private int bucketCount;
private long[] bucketStart; private long[] bucketStart;
private long[] rxBytes; private long[] rxBytes;
private long[] rxPackets;
private long[] txBytes; private long[] txBytes;
private long[] txPackets;
private int[] operations;
public static class Entry { public static class Entry {
public long bucketStart; public long bucketStart;
public long bucketDuration; public long bucketDuration;
public long rxBytes; public long rxBytes;
public long rxPackets;
public long txBytes; public long txBytes;
public long txPackets;
public int operations;
} }
public NetworkStatsHistory(long bucketDuration) { public NetworkStatsHistory(long bucketDuration) {
@@ -66,15 +83,21 @@ public class NetworkStatsHistory implements Parcelable {
this.bucketDuration = bucketDuration; this.bucketDuration = bucketDuration;
bucketStart = new long[initialSize]; bucketStart = new long[initialSize];
rxBytes = new long[initialSize]; rxBytes = new long[initialSize];
rxPackets = new long[initialSize];
txBytes = new long[initialSize]; txBytes = new long[initialSize];
txPackets = new long[initialSize];
operations = new int[initialSize];
bucketCount = 0; bucketCount = 0;
} }
public NetworkStatsHistory(Parcel in) { public NetworkStatsHistory(Parcel in) {
bucketDuration = in.readLong(); bucketDuration = in.readLong();
bucketStart = readLongArray(in); bucketStart = readLongArray(in);
rxBytes = in.createLongArray(); rxBytes = readLongArray(in);
txBytes = in.createLongArray(); rxPackets = readLongArray(in);
txBytes = readLongArray(in);
txPackets = readLongArray(in);
operations = readIntArray(in);
bucketCount = bucketStart.length; bucketCount = bucketStart.length;
} }
@@ -83,17 +106,24 @@ public class NetworkStatsHistory implements Parcelable {
out.writeLong(bucketDuration); out.writeLong(bucketDuration);
writeLongArray(out, bucketStart, bucketCount); writeLongArray(out, bucketStart, bucketCount);
writeLongArray(out, rxBytes, bucketCount); writeLongArray(out, rxBytes, bucketCount);
writeLongArray(out, rxPackets, bucketCount);
writeLongArray(out, txBytes, bucketCount); writeLongArray(out, txBytes, bucketCount);
writeLongArray(out, txPackets, bucketCount);
writeIntArray(out, operations, bucketCount);
} }
public NetworkStatsHistory(DataInputStream in) throws IOException { public NetworkStatsHistory(DataInputStream in) throws IOException {
// TODO: read packet and operation counts
final int version = in.readInt(); final int version = in.readInt();
switch (version) { switch (version) {
case VERSION_INIT: { case VERSION_INIT: {
bucketDuration = in.readLong(); bucketDuration = in.readLong();
bucketStart = readLongArray(in); bucketStart = readLongArray(in);
rxBytes = readLongArray(in); rxBytes = readLongArray(in);
rxPackets = new long[bucketStart.length];
txBytes = readLongArray(in); txBytes = readLongArray(in);
txPackets = new long[bucketStart.length];
operations = new int[bucketStart.length];
bucketCount = bucketStart.length; bucketCount = bucketStart.length;
break; break;
} }
@@ -104,6 +134,7 @@ public class NetworkStatsHistory implements Parcelable {
} }
public void writeToStream(DataOutputStream out) throws IOException { public void writeToStream(DataOutputStream out) throws IOException {
// TODO: write packet and operation counts
out.writeInt(VERSION_INIT); out.writeInt(VERSION_INIT);
out.writeLong(bucketDuration); out.writeLong(bucketDuration);
writeLongArray(out, bucketStart, bucketCount); writeLongArray(out, bucketStart, bucketCount);
@@ -148,7 +179,10 @@ public class NetworkStatsHistory implements Parcelable {
entry.bucketStart = bucketStart[i]; entry.bucketStart = bucketStart[i];
entry.bucketDuration = bucketDuration; entry.bucketDuration = bucketDuration;
entry.rxBytes = rxBytes[i]; entry.rxBytes = rxBytes[i];
entry.rxPackets = rxPackets[i];
entry.txBytes = txBytes[i]; entry.txBytes = txBytes[i];
entry.txPackets = txPackets[i];
entry.operations = operations[i];
return entry; return entry;
} }
@@ -156,17 +190,27 @@ public class NetworkStatsHistory implements Parcelable {
* Record that data traffic occurred in the given time range. Will * Record that data traffic occurred in the given time range. Will
* distribute across internal buckets, creating new buckets as needed. * distribute across internal buckets, creating new buckets as needed.
*/ */
public void recordData(long start, long end, long rx, long tx) { @Deprecated
if (rx < 0 || tx < 0) { public void recordData(long start, long end, long rxBytes, long txBytes) {
throw new IllegalArgumentException( recordData(start, end,
"tried recording negative data: rx=" + rx + ", tx=" + tx); new NetworkStats.Entry(IFACE_ALL, UID_ALL, TAG_NONE, rxBytes, 0L, txBytes, 0L, 0));
}
/**
* Record that data traffic occurred in the given time range. Will
* 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) {
throw new IllegalArgumentException("tried recording negative data");
} }
// create any buckets needed by this range // create any buckets needed by this range
ensureBuckets(start, end); ensureBuckets(start, end);
// distribute data usage into buckets // distribute data usage into buckets
final long duration = end - start; long duration = end - start;
for (int i = bucketCount - 1; i >= 0; i--) { for (int i = bucketCount - 1; i >= 0; i--) {
final long curStart = bucketStart[i]; final long curStart = bucketStart[i];
final long curEnd = curStart + bucketDuration; final long curEnd = curStart + bucketDuration;
@@ -177,10 +221,22 @@ public class NetworkStatsHistory implements Parcelable {
if (curStart > end) continue; if (curStart > end) continue;
final long overlap = Math.min(curEnd, end) - Math.max(curStart, start); final long overlap = Math.min(curEnd, end) - Math.max(curStart, start);
if (overlap > 0) { if (overlap <= 0) continue;
this.rxBytes[i] += rx * overlap / duration;
this.txBytes[i] += tx * overlap / duration; // 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 int fracOperations = (int) (entry.operations * overlap / duration);
rxBytes[i] += fracRxBytes; entry.rxBytes -= fracRxBytes;
rxPackets[i] += fracRxPackets; entry.rxPackets -= fracRxPackets;
txBytes[i] += fracTxBytes; entry.txBytes -= fracTxBytes;
txPackets[i] += fracTxPackets; entry.txPackets -= fracTxPackets;
operations[i] += fracOperations; entry.operations -= fracOperations;
duration -= overlap;
} }
} }
@@ -189,10 +245,19 @@ public class NetworkStatsHistory implements Parcelable {
* for combining together stats for external reporting. * for combining together stats for external reporting.
*/ */
public void recordEntireHistory(NetworkStatsHistory input) { public void recordEntireHistory(NetworkStatsHistory input) {
final NetworkStats.Entry entry = new NetworkStats.Entry(
IFACE_ALL, UID_ALL, TAG_NONE, 0L, 0L, 0L, 0L, 0);
for (int i = 0; i < input.bucketCount; i++) { for (int i = 0; i < input.bucketCount; i++) {
final long start = input.bucketStart[i]; final long start = input.bucketStart[i];
final long end = start + input.bucketDuration; final long end = start + input.bucketDuration;
recordData(start, end, input.rxBytes[i], input.txBytes[i]);
entry.rxBytes = input.rxBytes[i];
entry.rxPackets = input.rxPackets[i];
entry.txBytes = input.txBytes[i];
entry.txPackets = input.txPackets[i];
entry.operations = input.operations[i];
recordData(start, end, entry);
} }
} }
@@ -223,7 +288,10 @@ public class NetworkStatsHistory implements Parcelable {
final int newLength = Math.max(bucketStart.length, 10) * 3 / 2; final int newLength = Math.max(bucketStart.length, 10) * 3 / 2;
bucketStart = Arrays.copyOf(bucketStart, newLength); bucketStart = Arrays.copyOf(bucketStart, newLength);
rxBytes = Arrays.copyOf(rxBytes, newLength); rxBytes = Arrays.copyOf(rxBytes, newLength);
rxPackets = Arrays.copyOf(rxPackets, newLength);
txBytes = Arrays.copyOf(txBytes, newLength); txBytes = Arrays.copyOf(txBytes, newLength);
txPackets = Arrays.copyOf(txPackets, newLength);
operations = Arrays.copyOf(operations, newLength);
} }
// create gap when inserting bucket in middle // create gap when inserting bucket in middle
@@ -233,12 +301,18 @@ public class NetworkStatsHistory implements Parcelable {
System.arraycopy(bucketStart, index, bucketStart, dstPos, length); System.arraycopy(bucketStart, index, bucketStart, dstPos, length);
System.arraycopy(rxBytes, index, rxBytes, dstPos, length); System.arraycopy(rxBytes, index, rxBytes, dstPos, length);
System.arraycopy(rxPackets, index, rxPackets, dstPos, length);
System.arraycopy(txBytes, index, txBytes, dstPos, length); System.arraycopy(txBytes, index, txBytes, dstPos, length);
System.arraycopy(txPackets, index, txPackets, dstPos, length);
System.arraycopy(operations, index, operations, dstPos, length);
} }
bucketStart[index] = start; bucketStart[index] = start;
rxBytes[index] = 0; rxBytes[index] = 0;
rxPackets[index] = 0;
txBytes[index] = 0; txBytes[index] = 0;
txPackets[index] = 0;
operations[index] = 0;
bucketCount++; bucketCount++;
} }
@@ -260,7 +334,10 @@ public class NetworkStatsHistory implements Parcelable {
final int length = bucketStart.length; final int length = bucketStart.length;
bucketStart = Arrays.copyOfRange(bucketStart, i, length); bucketStart = Arrays.copyOfRange(bucketStart, i, length);
rxBytes = Arrays.copyOfRange(rxBytes, i, length); rxBytes = Arrays.copyOfRange(rxBytes, i, length);
rxPackets = Arrays.copyOfRange(rxPackets, i, length);
txBytes = Arrays.copyOfRange(txBytes, i, length); txBytes = Arrays.copyOfRange(txBytes, i, length);
txPackets = Arrays.copyOfRange(txPackets, i, length);
operations = Arrays.copyOfRange(operations, i, length);
bucketCount -= i; bucketCount -= i;
} }
} }
@@ -282,7 +359,10 @@ public class NetworkStatsHistory implements Parcelable {
entry.bucketStart = start; entry.bucketStart = start;
entry.bucketDuration = end - start; entry.bucketDuration = end - start;
entry.rxBytes = 0; entry.rxBytes = 0;
entry.rxPackets = 0;
entry.txBytes = 0; entry.txBytes = 0;
entry.txPackets = 0;
entry.operations = 0;
for (int i = bucketCount - 1; i >= 0; i--) { for (int i = bucketCount - 1; i >= 0; i--) {
final long curStart = bucketStart[i]; final long curStart = bucketStart[i];
@@ -295,14 +375,16 @@ public class NetworkStatsHistory implements Parcelable {
// include full value for active buckets, otherwise only fractional // include full value for active buckets, otherwise only fractional
final boolean activeBucket = curStart < now && curEnd > now; final boolean activeBucket = curStart < now && curEnd > now;
final long overlap = Math.min(curEnd, end) - Math.max(curStart, start); final long overlap = activeBucket ? bucketDuration
if (activeBucket || overlap == bucketDuration) { : Math.min(curEnd, end) - Math.max(curStart, start);
entry.rxBytes += rxBytes[i]; if (overlap <= 0) continue;
entry.txBytes += txBytes[i];
} else if (overlap > 0) { // integer math each time is faster than floating point
entry.rxBytes += rxBytes[i] * overlap / bucketDuration; entry.rxBytes += rxBytes[i] * overlap / bucketDuration;
entry.rxPackets += rxPackets[i] * overlap / bucketDuration;
entry.txBytes += txBytes[i] * overlap / bucketDuration; entry.txBytes += txBytes[i] * overlap / bucketDuration;
} entry.txPackets += txPackets[i] * overlap / bucketDuration;
entry.operations += operations[i] * overlap / bucketDuration;
} }
return entry; return entry;
@@ -315,17 +397,19 @@ public class NetworkStatsHistory implements Parcelable {
public void generateRandom(long start, long end, long rx, long tx) { public void generateRandom(long start, long end, long rx, long tx) {
ensureBuckets(start, end); ensureBuckets(start, end);
final NetworkStats.Entry entry = new NetworkStats.Entry(
IFACE_ALL, UID_ALL, TAG_NONE, 0L, 0L, 0L, 0L, 0);
final Random r = new Random(); final Random r = new Random();
while (rx > 1024 && tx > 1024) { while (rx > 1024 && tx > 1024) {
final long curStart = randomLong(r, start, end); final long curStart = randomLong(r, start, end);
final long curEnd = randomLong(r, curStart, end); final long curEnd = randomLong(r, curStart, end);
final long curRx = randomLong(r, 0, rx); entry.rxBytes = randomLong(r, 0, rx);
final long curTx = randomLong(r, 0, tx); entry.txBytes = randomLong(r, 0, tx);
recordData(curStart, curEnd, curRx, curTx); recordData(curStart, curEnd, entry);
rx -= curRx; rx -= entry.rxBytes;
tx -= curTx; tx -= entry.txBytes;
} }
} }
@@ -347,7 +431,10 @@ public class NetworkStatsHistory implements Parcelable {
pw.print(prefix); pw.print(prefix);
pw.print(" bucketStart="); pw.print(bucketStart[i]); pw.print(" bucketStart="); pw.print(bucketStart[i]);
pw.print(" rxBytes="); pw.print(rxBytes[i]); pw.print(" rxBytes="); pw.print(rxBytes[i]);
pw.print(" txBytes="); pw.println(txBytes[i]); pw.print(" rxPackets="); pw.print(rxPackets[i]);
pw.print(" txBytes="); pw.print(txBytes[i]);
pw.print(" txPackets="); pw.print(txPackets[i]);
pw.print(" operations="); pw.println(operations[i]);
} }
} }
@@ -368,7 +455,12 @@ public class NetworkStatsHistory implements Parcelable {
} }
}; };
private static long[] readLongArray(DataInputStream in) throws IOException { /**
* Utility methods for interacting with {@link DataInputStream} and
* {@link DataOutputStream}, mostly dealing with writing partial arrays.
*/
public static class DataStreamUtils {
public static long[] readLongArray(DataInputStream in) throws IOException {
final int size = in.readInt(); final int size = in.readInt();
final long[] values = new long[size]; final long[] values = new long[size];
for (int i = 0; i < values.length; i++) { for (int i = 0; i < values.length; i++) {
@@ -377,7 +469,33 @@ public class NetworkStatsHistory implements Parcelable {
return values; return values;
} }
private static void writeLongArray(DataOutputStream out, long[] values, int size) throws IOException { public static void writeLongArray(DataOutputStream out, long[] values, int size)
throws IOException {
if (size > values.length) {
throw new IllegalArgumentException("size larger than length");
}
out.writeInt(size);
for (int i = 0; i < size; i++) {
out.writeLong(values[i]);
}
}
}
/**
* Utility methods for interacting with {@link Parcel} structures, mostly
* dealing with writing partial arrays.
*/
public static class ParcelUtils {
public static long[] readLongArray(Parcel in) {
final int size = in.readInt();
final long[] values = new long[size];
for (int i = 0; i < values.length; i++) {
values[i] = in.readLong();
}
return values;
}
public static void writeLongArray(Parcel out, long[] values, int size) {
if (size > values.length) { if (size > values.length) {
throw new IllegalArgumentException("size larger than length"); throw new IllegalArgumentException("size larger than length");
} }
@@ -387,22 +505,23 @@ public class NetworkStatsHistory implements Parcelable {
} }
} }
private static long[] readLongArray(Parcel in) { public static int[] readIntArray(Parcel in) {
final int size = in.readInt(); final int size = in.readInt();
final long[] values = new long[size]; final int[] values = new int[size];
for (int i = 0; i < values.length; i++) { for (int i = 0; i < values.length; i++) {
values[i] = in.readLong(); values[i] = in.readInt();
} }
return values; return values;
} }
private static void writeLongArray(Parcel out, long[] values, int size) { public static void writeIntArray(Parcel out, int[] values, int size) {
if (size > values.length) { if (size > values.length) {
throw new IllegalArgumentException("size larger than length"); throw new IllegalArgumentException("size larger than length");
} }
out.writeInt(size); out.writeInt(size);
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
out.writeLong(values[i]); out.writeInt(values[i]);
}
} }
} }

View File

@@ -20,14 +20,13 @@ import android.app.DownloadManager;
import android.app.backup.BackupManager; import android.app.backup.BackupManager;
import android.content.Context; import android.content.Context;
import android.media.MediaPlayer; import android.media.MediaPlayer;
import android.os.IBinder;
import android.os.INetworkManagementService;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.ServiceManager; import android.os.ServiceManager;
import com.android.server.NetworkManagementSocketTagger; import com.android.server.NetworkManagementSocketTagger;
import dalvik.system.SocketTagger; import dalvik.system.SocketTagger;
import java.net.Socket; import java.net.Socket;
import java.net.SocketException; import java.net.SocketException;
@@ -172,7 +171,7 @@ public class TrafficStats {
} }
// take snapshot in time; we calculate delta later // take snapshot in time; we calculate delta later
sActiveProfilingStart = getNetworkStatsForUid(context); sActiveProfilingStart = getDataLayerSnapshotForUid(context);
} }
} }
@@ -190,7 +189,7 @@ public class TrafficStats {
} }
// subtract starting values and return delta // subtract starting values and return delta
final NetworkStats profilingStop = getNetworkStatsForUid(context); final NetworkStats profilingStop = getDataLayerSnapshotForUid(context);
final NetworkStats profilingDelta = profilingStop.subtractClamped( final NetworkStats profilingDelta = profilingStop.subtractClamped(
sActiveProfilingStart); sActiveProfilingStart);
sActiveProfilingStart = null; sActiveProfilingStart = null;
@@ -198,6 +197,28 @@ public class TrafficStats {
} }
} }
/**
* Increment count of network operations performed under the given
* accounting tag. This can be used to derive bytes-per-operation.
*
* @param tag Accounting tag used in {@link #setThreadStatsTag(int)}.
* @param operationCount Number of operations to increment count by.
*/
public static void incrementOperationCount(int tag, int operationCount) {
if (operationCount < 0) {
throw new IllegalArgumentException("operation count can only be incremented");
}
final INetworkStatsService statsService = INetworkStatsService.Stub.asInterface(
ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
final int uid = android.os.Process.myUid();
try {
statsService.incrementOperationCount(uid, tag, operationCount);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
/** /**
* Get the total number of packets transmitted through the mobile interface. * Get the total number of packets transmitted through the mobile interface.
* *
@@ -461,14 +482,12 @@ public class TrafficStats {
* Return detailed {@link NetworkStats} for the current UID. Requires no * Return detailed {@link NetworkStats} for the current UID. Requires no
* special permission. * special permission.
*/ */
private static NetworkStats getNetworkStatsForUid(Context context) { private static NetworkStats getDataLayerSnapshotForUid(Context context) {
final IBinder binder = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); final INetworkStatsService statsService = INetworkStatsService.Stub.asInterface(
final INetworkManagementService service = INetworkManagementService.Stub.asInterface( ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
binder);
final int uid = android.os.Process.myUid(); final int uid = android.os.Process.myUid();
try { try {
return service.getNetworkStatsUidDetail(uid); return statsService.getDataLayerSnapshotForUid(uid);
} catch (RemoteException e) { } catch (RemoteException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }

View File

@@ -17,6 +17,8 @@
package com.android.server.net; package com.android.server.net;
import static android.Manifest.permission.CONNECTIVITY_INTERNAL; import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
import static android.Manifest.permission.ACCESS_NETWORK_STATE;
import static android.Manifest.permission.MODIFY_NETWORK_ACCOUNTING;
import static android.Manifest.permission.DUMP; import static android.Manifest.permission.DUMP;
import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY; import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
import static android.content.Intent.ACTION_SHUTDOWN; import static android.content.Intent.ACTION_SHUTDOWN;
@@ -56,6 +58,7 @@ import android.net.NetworkState;
import android.net.NetworkStats; import android.net.NetworkStats;
import android.net.NetworkStatsHistory; import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate; import android.net.NetworkTemplate;
import android.os.Binder;
import android.os.Environment; import android.os.Environment;
import android.os.Handler; import android.os.Handler;
import android.os.HandlerThread; import android.os.HandlerThread;
@@ -65,6 +68,7 @@ import android.os.RemoteException;
import android.os.SystemClock; import android.os.SystemClock;
import android.provider.Settings; import android.provider.Settings;
import android.telephony.TelephonyManager; import android.telephony.TelephonyManager;
import android.util.Log;
import android.util.LongSparseArray; import android.util.LongSparseArray;
import android.util.NtpTrustedTime; import android.util.NtpTrustedTime;
import android.util.Slog; import android.util.Slog;
@@ -150,9 +154,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
/** Set of currently active ifaces. */ /** Set of currently active ifaces. */
private HashMap<String, NetworkIdentitySet> mActiveIfaces = Maps.newHashMap(); private HashMap<String, NetworkIdentitySet> mActiveIfaces = Maps.newHashMap();
/** Set of historical stats for known networks. */ /** Set of historical network layer stats for known networks. */
private HashMap<NetworkIdentitySet, NetworkStatsHistory> mNetworkStats = Maps.newHashMap(); private HashMap<NetworkIdentitySet, NetworkStatsHistory> mNetworkStats = Maps.newHashMap();
/** Set of historical stats for known UIDs. */ /** Set of historical network layer stats for known UIDs. */
private HashMap<NetworkIdentitySet, LongSparseArray<NetworkStatsHistory>> mUidStats = private HashMap<NetworkIdentitySet, LongSparseArray<NetworkStatsHistory>> mUidStats =
Maps.newHashMap(); Maps.newHashMap();
@@ -164,6 +168,10 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
private NetworkStats mLastUidSnapshot; private NetworkStats mLastUidSnapshot;
/** Data layer operation counters for splicing into other structures. */
private NetworkStats mOperations = new NetworkStats(0L, 10);
private NetworkStats mLastOperationsSnapshot;
private final HandlerThread mHandlerThread; private final HandlerThread mHandlerThread;
private final Handler mHandler; private final Handler mHandler;
@@ -381,9 +389,13 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
entry.uid = uid; entry.uid = uid;
entry.tag = tag; entry.tag = tag;
entry.rxBytes = historyEntry.rxBytes; entry.rxBytes = historyEntry.rxBytes;
entry.rxPackets = historyEntry.rxPackets;
entry.txBytes = historyEntry.txBytes; entry.txBytes = historyEntry.txBytes;
entry.txPackets = historyEntry.txPackets;
entry.operations = historyEntry.operations;
if (entry.rxBytes > 0 || entry.txBytes > 0) { if (entry.rxBytes > 0 || entry.rxPackets > 0 || entry.txBytes > 0
|| entry.txPackets > 0 || entry.operations > 0) {
stats.combineValues(entry); stats.combineValues(entry);
} }
} }
@@ -395,6 +407,41 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
} }
} }
@Override
public NetworkStats getDataLayerSnapshotForUid(int uid) throws RemoteException {
if (Binder.getCallingUid() != uid) {
mContext.enforceCallingOrSelfPermission(ACCESS_NETWORK_STATE, TAG);
}
// TODO: switch to data layer stats once kernel exports
// for now, read network layer stats and flatten across all ifaces
final NetworkStats networkLayer = mNetworkManager.getNetworkStatsUidDetail(uid);
final NetworkStats dataLayer = new NetworkStats(
networkLayer.getElapsedRealtime(), networkLayer.size());
NetworkStats.Entry entry = null;
for (int i = 0; i < networkLayer.size(); i++) {
entry = networkLayer.getValues(i, entry);
entry.iface = IFACE_ALL;
dataLayer.combineValues(entry);
}
// splice in operation counts
dataLayer.spliceOperationsFrom(mOperations);
return dataLayer;
}
@Override
public void incrementOperationCount(int uid, int tag, int operationCount) {
if (Binder.getCallingUid() != uid) {
mContext.enforceCallingOrSelfPermission(MODIFY_NETWORK_ACCOUNTING, TAG);
}
synchronized (mStatsLock) {
mOperations.combineValues(IFACE_ALL, uid, tag, 0L, 0L, 0L, 0L, operationCount);
}
}
@Override @Override
public void forceUpdate() { public void forceUpdate() {
mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG); mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
@@ -533,7 +580,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
final NetworkStats uidSnapshot; final NetworkStats uidSnapshot;
try { try {
networkSnapshot = mNetworkManager.getNetworkStatsSummary(); networkSnapshot = mNetworkManager.getNetworkStatsSummary();
uidSnapshot = detailedPoll ? mNetworkManager.getNetworkStatsDetail() : null; uidSnapshot = detailedPoll ? mNetworkManager.getNetworkStatsUidDetail(UID_ALL) : null;
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
Slog.w(TAG, "problem reading network stats: " + e); Slog.w(TAG, "problem reading network stats: " + e);
return; return;
@@ -592,7 +639,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
} }
final NetworkStatsHistory history = findOrCreateNetworkStatsLocked(ident); final NetworkStatsHistory history = findOrCreateNetworkStatsLocked(ident);
history.recordData(timeStart, currentTime, entry.rxBytes, entry.txBytes); history.recordData(timeStart, currentTime, entry);
} }
// trim any history beyond max // trim any history beyond max
@@ -615,9 +662,12 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
ensureUidStatsLoadedLocked(); ensureUidStatsLoadedLocked();
final NetworkStats delta = computeStatsDelta(mLastUidSnapshot, uidSnapshot); final NetworkStats delta = computeStatsDelta(mLastUidSnapshot, uidSnapshot);
final NetworkStats operationsDelta = computeStatsDelta(
mLastOperationsSnapshot, mOperations);
final long timeStart = currentTime - delta.getElapsedRealtime(); final long timeStart = currentTime - delta.getElapsedRealtime();
NetworkStats.Entry entry = null; NetworkStats.Entry entry = null;
NetworkStats.Entry operationsEntry = null;
for (int i = 0; i < delta.size(); i++) { for (int i = 0; i < delta.size(); i++) {
entry = delta.getValues(i, entry); entry = delta.getValues(i, entry);
final NetworkIdentitySet ident = mActiveIfaces.get(entry.iface); final NetworkIdentitySet ident = mActiveIfaces.get(entry.iface);
@@ -625,9 +675,16 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
continue; continue;
} }
// splice in operation counts since last poll
final int j = operationsDelta.findIndex(IFACE_ALL, entry.uid, entry.tag);
if (j != -1) {
operationsEntry = operationsDelta.getValues(j, operationsEntry);
entry.operations = operationsEntry.operations;
}
final NetworkStatsHistory history = findOrCreateUidStatsLocked( final NetworkStatsHistory history = findOrCreateUidStatsLocked(
ident, entry.uid, entry.tag); ident, entry.uid, entry.tag);
history.recordData(timeStart, currentTime, entry.rxBytes, entry.txBytes); history.recordData(timeStart, currentTime, entry);
} }
// trim any history beyond max // trim any history beyond max
@@ -648,6 +705,8 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
} }
mLastUidSnapshot = uidSnapshot; mLastUidSnapshot = uidSnapshot;
mLastOperationsSnapshot = mOperations;
mOperations = new NetworkStats(0L, 10);
} }
/** /**
@@ -980,7 +1039,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
final int tag = unpackTag(packed); final int tag = unpackTag(packed);
final NetworkStatsHistory history = uidStats.valueAt(i); final NetworkStatsHistory history = uidStats.valueAt(i);
pw.print(" UID="); pw.print(uid); pw.print(" UID="); pw.print(uid);
pw.print(" tag="); pw.println(tag); pw.print(" tag=0x"); pw.println(Integer.toHexString(tag));
history.dump(" ", pw, fullHistory); history.dump(" ", pw, fullHistory);
} }
} }
@@ -1028,7 +1087,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
if (before != null) { if (before != null) {
return current.subtractClamped(before); return current.subtractClamped(before);
} else { } else {
return current; // this is first snapshot; to prevent from double-counting we only
// observe traffic occuring between known snapshots.
return new NetworkStats(0L, 10);
} }
} }
@@ -1114,5 +1175,4 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
return DAY_IN_MILLIS; return DAY_IN_MILLIS;
} }
} }
} }