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
diff --git a/core/java/android/net/INetworkStatsService.aidl b/core/java/android/net/INetworkStatsService.aidl
index 0548250..c41d182 100644
--- a/core/java/android/net/INetworkStatsService.aidl
+++ b/core/java/android/net/INetworkStatsService.aidl
@@ -23,16 +23,21 @@
/** {@hide} */
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);
- /** 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);
- /** 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);
- /** 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);
+ /** 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. */
void forceUpdate();
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index fbff7d8..0e8e7fc 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -21,6 +21,8 @@
import android.os.SystemClock;
import android.util.SparseBooleanArray;
+import com.android.internal.util.Objects;
+
import java.io.CharArrayWriter;
import java.io.PrintWriter;
import java.util.Arrays;
@@ -56,6 +58,7 @@
private long[] rxPackets;
private long[] txBytes;
private long[] txPackets;
+ private int[] operations;
public static class Entry {
public String iface;
@@ -65,12 +68,13 @@
public long rxPackets;
public long txBytes;
public long txPackets;
+ public int operations;
public Entry() {
}
public Entry(String iface, int uid, int tag, long rxBytes, long rxPackets, long txBytes,
- long txPackets) {
+ long txPackets, int operations) {
this.iface = iface;
this.uid = uid;
this.tag = tag;
@@ -78,6 +82,7 @@
this.rxPackets = rxPackets;
this.txBytes = txBytes;
this.txPackets = txPackets;
+ this.operations = operations;
}
}
@@ -91,6 +96,7 @@
this.rxPackets = new long[initialSize];
this.txBytes = new long[initialSize];
this.txPackets = new long[initialSize];
+ this.operations = new int[initialSize];
}
public NetworkStats(Parcel parcel) {
@@ -103,11 +109,32 @@
rxPackets = parcel.createLongArray();
txBytes = 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,
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 @@
rxPackets = Arrays.copyOf(rxPackets, newLength);
txBytes = Arrays.copyOf(txBytes, newLength);
txPackets = Arrays.copyOf(txPackets, newLength);
+ operations = Arrays.copyOf(operations, newLength);
}
iface[size] = entry.iface;
@@ -133,6 +161,7 @@
rxPackets[size] = entry.rxPackets;
txBytes[size] = entry.txBytes;
txPackets[size] = entry.txPackets;
+ operations[size] = entry.operations;
size++;
return this;
@@ -150,6 +179,7 @@
entry.rxPackets = rxPackets[i];
entry.txBytes = txBytes[i];
entry.txPackets = txPackets[i];
+ entry.operations = operations[i];
return entry;
}
@@ -167,8 +197,9 @@
}
public NetworkStats combineValues(String iface, int uid, int tag, long rxBytes, long rxPackets,
- long txBytes, long txPackets) {
- return combineValues(new Entry(iface, uid, tag, rxBytes, rxPackets, txBytes, txPackets));
+ long txBytes, long txPackets, int operations) {
+ return combineValues(
+ new Entry(iface, uid, tag, rxBytes, rxPackets, txBytes, txPackets, operations));
}
/**
@@ -186,6 +217,7 @@
rxPackets[i] += entry.rxPackets;
txBytes[i] += entry.txBytes;
txPackets[i] += entry.txPackets;
+ operations[i] += entry.operations;
}
return this;
}
@@ -195,7 +227,7 @@
*/
public int findIndex(String iface, int uid, int tag) {
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;
}
}
@@ -203,6 +235,22 @@
}
/**
+ * 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.
*/
public String[] getUniqueIfaces() {
@@ -289,15 +337,17 @@
entry.rxPackets = rxPackets[i];
entry.txBytes = txBytes[i];
entry.txPackets = txPackets[i];
+ entry.operations = operations[i];
} else {
// existing row, subtract remote value
entry.rxBytes = rxBytes[i] - value.rxBytes[j];
entry.rxPackets = rxPackets[i] - value.rxPackets[j];
entry.txBytes = txBytes[i] - value.txBytes[j];
entry.txPackets = txPackets[i] - value.txPackets[j];
+ entry.operations = operations[i] - value.operations[j];
if (enforceMonotonic
&& (entry.rxBytes < 0 || entry.rxPackets < 0 || entry.txBytes < 0
- || entry.txPackets < 0)) {
+ || entry.txPackets < 0 || entry.operations < 0)) {
throw new IllegalArgumentException("found non-monotonic values");
}
if (clampNegative) {
@@ -305,6 +355,7 @@
entry.rxPackets = Math.max(0, entry.rxPackets);
entry.txBytes = Math.max(0, entry.txBytes);
entry.txPackets = Math.max(0, entry.txPackets);
+ entry.operations = Math.max(0, entry.operations);
}
}
@@ -314,10 +365,6 @@
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) {
pw.print(prefix);
pw.print("NetworkStats: elapsedRealtime="); pw.println(elapsedRealtime);
@@ -325,11 +372,12 @@
pw.print(prefix);
pw.print(" iface="); pw.print(iface[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(" rxPackets="); pw.print(rxPackets[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 @@
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 NetworkStats createFromParcel(Parcel in) {
return new NetworkStats(in);
diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java
index 8bd1738..4ffabb1 100644
--- a/core/java/android/net/NetworkStatsHistory.java
+++ b/core/java/android/net/NetworkStatsHistory.java
@@ -16,6 +16,16 @@
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.Parcelable;
@@ -43,19 +53,26 @@
private static final int VERSION_INIT = 1;
// 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 int bucketCount;
private long[] bucketStart;
private long[] rxBytes;
+ private long[] rxPackets;
private long[] txBytes;
+ private long[] txPackets;
+ private int[] operations;
public static class Entry {
public long bucketStart;
public long bucketDuration;
public long rxBytes;
+ public long rxPackets;
public long txBytes;
+ public long txPackets;
+ public int operations;
}
public NetworkStatsHistory(long bucketDuration) {
@@ -66,15 +83,21 @@
this.bucketDuration = bucketDuration;
bucketStart = new long[initialSize];
rxBytes = new long[initialSize];
+ rxPackets = new long[initialSize];
txBytes = new long[initialSize];
+ txPackets = new long[initialSize];
+ operations = new int[initialSize];
bucketCount = 0;
}
public NetworkStatsHistory(Parcel in) {
bucketDuration = in.readLong();
bucketStart = readLongArray(in);
- rxBytes = in.createLongArray();
- txBytes = in.createLongArray();
+ rxBytes = readLongArray(in);
+ rxPackets = readLongArray(in);
+ txBytes = readLongArray(in);
+ txPackets = readLongArray(in);
+ operations = readIntArray(in);
bucketCount = bucketStart.length;
}
@@ -83,17 +106,24 @@
out.writeLong(bucketDuration);
writeLongArray(out, bucketStart, bucketCount);
writeLongArray(out, rxBytes, bucketCount);
+ writeLongArray(out, rxPackets, bucketCount);
writeLongArray(out, txBytes, bucketCount);
+ writeLongArray(out, txPackets, bucketCount);
+ writeIntArray(out, operations, bucketCount);
}
public NetworkStatsHistory(DataInputStream in) throws IOException {
+ // TODO: read packet and operation counts
final int version = in.readInt();
switch (version) {
case VERSION_INIT: {
bucketDuration = in.readLong();
bucketStart = readLongArray(in);
rxBytes = readLongArray(in);
+ rxPackets = new long[bucketStart.length];
txBytes = readLongArray(in);
+ txPackets = new long[bucketStart.length];
+ operations = new int[bucketStart.length];
bucketCount = bucketStart.length;
break;
}
@@ -104,6 +134,7 @@
}
public void writeToStream(DataOutputStream out) throws IOException {
+ // TODO: write packet and operation counts
out.writeInt(VERSION_INIT);
out.writeLong(bucketDuration);
writeLongArray(out, bucketStart, bucketCount);
@@ -148,7 +179,10 @@
entry.bucketStart = bucketStart[i];
entry.bucketDuration = bucketDuration;
entry.rxBytes = rxBytes[i];
+ entry.rxPackets = rxPackets[i];
entry.txBytes = txBytes[i];
+ entry.txPackets = txPackets[i];
+ entry.operations = operations[i];
return entry;
}
@@ -156,17 +190,27 @@
* 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, long rx, long tx) {
- if (rx < 0 || tx < 0) {
- throw new IllegalArgumentException(
- "tried recording negative data: rx=" + rx + ", tx=" + tx);
+ @Deprecated
+ public void recordData(long start, long end, long rxBytes, long txBytes) {
+ recordData(start, end,
+ 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
ensureBuckets(start, end);
// distribute data usage into buckets
- final long duration = end - start;
+ long duration = end - start;
for (int i = bucketCount - 1; i >= 0; i--) {
final long curStart = bucketStart[i];
final long curEnd = curStart + bucketDuration;
@@ -177,10 +221,22 @@
if (curStart > end) continue;
final long overlap = Math.min(curEnd, end) - Math.max(curStart, start);
- if (overlap > 0) {
- this.rxBytes[i] += rx * overlap / duration;
- this.txBytes[i] += tx * overlap / duration;
- }
+ 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 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 @@
* for combining together stats for external reporting.
*/
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++) {
final long start = input.bucketStart[i];
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 @@
final int newLength = Math.max(bucketStart.length, 10) * 3 / 2;
bucketStart = Arrays.copyOf(bucketStart, newLength);
rxBytes = Arrays.copyOf(rxBytes, newLength);
+ rxPackets = Arrays.copyOf(rxPackets, newLength);
txBytes = Arrays.copyOf(txBytes, newLength);
+ txPackets = Arrays.copyOf(txPackets, newLength);
+ operations = Arrays.copyOf(operations, newLength);
}
// create gap when inserting bucket in middle
@@ -233,12 +301,18 @@
System.arraycopy(bucketStart, index, bucketStart, 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(txPackets, index, txPackets, dstPos, length);
+ System.arraycopy(operations, index, operations, dstPos, length);
}
bucketStart[index] = start;
rxBytes[index] = 0;
+ rxPackets[index] = 0;
txBytes[index] = 0;
+ txPackets[index] = 0;
+ operations[index] = 0;
bucketCount++;
}
@@ -260,7 +334,10 @@
final int length = bucketStart.length;
bucketStart = Arrays.copyOfRange(bucketStart, i, length);
rxBytes = Arrays.copyOfRange(rxBytes, i, length);
+ rxPackets = Arrays.copyOfRange(rxPackets, i, length);
txBytes = Arrays.copyOfRange(txBytes, i, length);
+ txPackets = Arrays.copyOfRange(txPackets, i, length);
+ operations = Arrays.copyOfRange(operations, i, length);
bucketCount -= i;
}
}
@@ -282,7 +359,10 @@
entry.bucketStart = start;
entry.bucketDuration = end - start;
entry.rxBytes = 0;
+ entry.rxPackets = 0;
entry.txBytes = 0;
+ entry.txPackets = 0;
+ entry.operations = 0;
for (int i = bucketCount - 1; i >= 0; i--) {
final long curStart = bucketStart[i];
@@ -295,14 +375,16 @@
// include full value for active buckets, otherwise only fractional
final boolean activeBucket = curStart < now && curEnd > now;
- final long overlap = Math.min(curEnd, end) - Math.max(curStart, start);
- if (activeBucket || overlap == bucketDuration) {
- entry.rxBytes += rxBytes[i];
- entry.txBytes += txBytes[i];
- } else if (overlap > 0) {
- entry.rxBytes += rxBytes[i] * overlap / bucketDuration;
- entry.txBytes += txBytes[i] * overlap / bucketDuration;
- }
+ final long overlap = activeBucket ? bucketDuration
+ : Math.min(curEnd, end) - Math.max(curStart, start);
+ if (overlap <= 0) continue;
+
+ // integer math each time is faster than floating point
+ entry.rxBytes += rxBytes[i] * overlap / bucketDuration;
+ entry.rxPackets += rxPackets[i] * overlap / bucketDuration;
+ entry.txBytes += txBytes[i] * overlap / bucketDuration;
+ entry.txPackets += txPackets[i] * overlap / bucketDuration;
+ entry.operations += operations[i] * overlap / bucketDuration;
}
return entry;
@@ -315,17 +397,19 @@
public void generateRandom(long start, long end, long rx, long tx) {
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();
while (rx > 1024 && tx > 1024) {
final long curStart = randomLong(r, start, end);
final long curEnd = randomLong(r, curStart, end);
- final long curRx = randomLong(r, 0, rx);
- final long curTx = randomLong(r, 0, tx);
+ entry.rxBytes = randomLong(r, 0, rx);
+ entry.txBytes = randomLong(r, 0, tx);
- recordData(curStart, curEnd, curRx, curTx);
+ recordData(curStart, curEnd, entry);
- rx -= curRx;
- tx -= curTx;
+ rx -= entry.rxBytes;
+ tx -= entry.txBytes;
}
}
@@ -347,7 +431,10 @@
pw.print(prefix);
pw.print(" bucketStart="); pw.print(bucketStart[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,41 +455,73 @@
}
};
- private static long[] readLongArray(DataInputStream in) throws IOException {
- final int size = in.readInt();
- final long[] values = new long[size];
- for (int i = 0; i < values.length; i++) {
- values[i] = in.readLong();
+ /**
+ * 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 long[] values = new long[size];
+ for (int i = 0; i < values.length; i++) {
+ values[i] = in.readLong();
+ }
+ return values;
}
- return values;
- }
- private 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]);
+ 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]);
+ }
}
}
- private 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();
+ /**
+ * 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;
}
- return values;
- }
- private static void writeLongArray(Parcel out, long[] values, int size) {
- if (size > values.length) {
- throw new IllegalArgumentException("size larger than length");
+ public static void writeLongArray(Parcel out, long[] values, int size) {
+ 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]);
+ }
}
- out.writeInt(size);
- for (int i = 0; i < size; i++) {
- out.writeLong(values[i]);
+
+ public static int[] readIntArray(Parcel in) {
+ final int size = in.readInt();
+ final int[] values = new int[size];
+ for (int i = 0; i < values.length; i++) {
+ values[i] = in.readInt();
+ }
+ return values;
+ }
+
+ public static void writeIntArray(Parcel out, int[] values, int size) {
+ if (size > values.length) {
+ throw new IllegalArgumentException("size larger than length");
+ }
+ out.writeInt(size);
+ for (int i = 0; i < size; i++) {
+ out.writeInt(values[i]);
+ }
}
}
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index e054930..f138e49 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -20,14 +20,13 @@
import android.app.backup.BackupManager;
import android.content.Context;
import android.media.MediaPlayer;
-import android.os.IBinder;
-import android.os.INetworkManagementService;
import android.os.RemoteException;
import android.os.ServiceManager;
import com.android.server.NetworkManagementSocketTagger;
import dalvik.system.SocketTagger;
+
import java.net.Socket;
import java.net.SocketException;
@@ -172,7 +171,7 @@
}
// take snapshot in time; we calculate delta later
- sActiveProfilingStart = getNetworkStatsForUid(context);
+ sActiveProfilingStart = getDataLayerSnapshotForUid(context);
}
}
@@ -190,7 +189,7 @@
}
// subtract starting values and return delta
- final NetworkStats profilingStop = getNetworkStatsForUid(context);
+ final NetworkStats profilingStop = getDataLayerSnapshotForUid(context);
final NetworkStats profilingDelta = profilingStop.subtractClamped(
sActiveProfilingStart);
sActiveProfilingStart = null;
@@ -199,6 +198,28 @@
}
/**
+ * 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.
*
* @return number of packets. If the statistics are not supported by this device,
@@ -461,14 +482,12 @@
* Return detailed {@link NetworkStats} for the current UID. Requires no
* special permission.
*/
- private static NetworkStats getNetworkStatsForUid(Context context) {
- final IBinder binder = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
- final INetworkManagementService service = INetworkManagementService.Stub.asInterface(
- binder);
-
+ private static NetworkStats getDataLayerSnapshotForUid(Context context) {
+ final INetworkStatsService statsService = INetworkStatsService.Stub.asInterface(
+ ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
final int uid = android.os.Process.myUid();
try {
- return service.getNetworkStatsUidDetail(uid);
+ return statsService.getDataLayerSnapshotForUid(uid);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
index 6cc01f4..24188ca 100644
--- a/services/java/com/android/server/net/NetworkStatsService.java
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -17,6 +17,8 @@
package com.android.server.net;
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.READ_NETWORK_USAGE_HISTORY;
import static android.content.Intent.ACTION_SHUTDOWN;
@@ -56,6 +58,7 @@
import android.net.NetworkStats;
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
+import android.os.Binder;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
@@ -65,6 +68,7 @@
import android.os.SystemClock;
import android.provider.Settings;
import android.telephony.TelephonyManager;
+import android.util.Log;
import android.util.LongSparseArray;
import android.util.NtpTrustedTime;
import android.util.Slog;
@@ -150,9 +154,9 @@
/** Set of currently active ifaces. */
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();
- /** Set of historical stats for known UIDs. */
+ /** Set of historical network layer stats for known UIDs. */
private HashMap<NetworkIdentitySet, LongSparseArray<NetworkStatsHistory>> mUidStats =
Maps.newHashMap();
@@ -164,6 +168,10 @@
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 Handler mHandler;
@@ -381,9 +389,13 @@
entry.uid = uid;
entry.tag = 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.txBytes > 0) {
+ if (entry.rxBytes > 0 || entry.rxPackets > 0 || entry.txBytes > 0
+ || entry.txPackets > 0 || entry.operations > 0) {
stats.combineValues(entry);
}
}
@@ -396,6 +408,41 @@
}
@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
public void forceUpdate() {
mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
@@ -533,7 +580,7 @@
final NetworkStats uidSnapshot;
try {
networkSnapshot = mNetworkManager.getNetworkStatsSummary();
- uidSnapshot = detailedPoll ? mNetworkManager.getNetworkStatsDetail() : null;
+ uidSnapshot = detailedPoll ? mNetworkManager.getNetworkStatsUidDetail(UID_ALL) : null;
} catch (IllegalStateException e) {
Slog.w(TAG, "problem reading network stats: " + e);
return;
@@ -592,7 +639,7 @@
}
final NetworkStatsHistory history = findOrCreateNetworkStatsLocked(ident);
- history.recordData(timeStart, currentTime, entry.rxBytes, entry.txBytes);
+ history.recordData(timeStart, currentTime, entry);
}
// trim any history beyond max
@@ -615,9 +662,12 @@
ensureUidStatsLoadedLocked();
final NetworkStats delta = computeStatsDelta(mLastUidSnapshot, uidSnapshot);
+ final NetworkStats operationsDelta = computeStatsDelta(
+ mLastOperationsSnapshot, mOperations);
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);
@@ -625,9 +675,16 @@
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(
ident, entry.uid, entry.tag);
- history.recordData(timeStart, currentTime, entry.rxBytes, entry.txBytes);
+ history.recordData(timeStart, currentTime, entry);
}
// trim any history beyond max
@@ -648,6 +705,8 @@
}
mLastUidSnapshot = uidSnapshot;
+ mLastOperationsSnapshot = mOperations;
+ mOperations = new NetworkStats(0L, 10);
}
/**
@@ -980,7 +1039,7 @@
final int tag = unpackTag(packed);
final NetworkStatsHistory history = uidStats.valueAt(i);
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);
}
}
@@ -1028,7 +1087,9 @@
if (before != null) {
return current.subtractClamped(before);
} 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 @@
return DAY_IN_MILLIS;
}
}
-
}