NetworkStatsHistorypublic class NetworkStatsHistory extends Object implements android.os.ParcelableCollection of historical network statistics, recorded into equally-sized
"buckets" in time. Internally it stores data in {@code long} series for more
efficient persistence.
Each bucket is defined by a {@link #bucketStart} timestamp, and lasts for
{@link #bucketDuration}. Internally assumes that {@link #bucketStart} is
sorted at all times. |
Fields Summary |
---|
private static final int | VERSION_INIT | private static final int | VERSION_ADD_PACKETS | private static final int | VERSION_ADD_ACTIVE | public static final int | FIELD_ACTIVE_TIME | public static final int | FIELD_RX_BYTES | public static final int | FIELD_RX_PACKETS | public static final int | FIELD_TX_BYTES | public static final int | FIELD_TX_PACKETS | public static final int | FIELD_OPERATIONS | public static final int | FIELD_ALL | private long | bucketDuration | private int | bucketCount | private long[] | bucketStart | private long[] | activeTime | private long[] | rxBytes | private long[] | rxPackets | private long[] | txBytes | private long[] | txPackets | private long[] | operations | private long | totalBytes | public static final Creator | CREATOR |
Constructors Summary |
---|
public NetworkStatsHistory(long bucketDuration)
this(bucketDuration, 10, FIELD_ALL);
| public NetworkStatsHistory(long bucketDuration, int initialSize)
this(bucketDuration, initialSize, FIELD_ALL);
| public NetworkStatsHistory(long bucketDuration, int initialSize, int fields)
this.bucketDuration = bucketDuration;
bucketStart = new long[initialSize];
if ((fields & FIELD_ACTIVE_TIME) != 0) activeTime = new long[initialSize];
if ((fields & FIELD_RX_BYTES) != 0) rxBytes = new long[initialSize];
if ((fields & FIELD_RX_PACKETS) != 0) rxPackets = new long[initialSize];
if ((fields & FIELD_TX_BYTES) != 0) txBytes = new long[initialSize];
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(android.os.Parcel in)
bucketDuration = in.readLong();
bucketStart = readLongArray(in);
activeTime = readLongArray(in);
rxBytes = readLongArray(in);
rxPackets = readLongArray(in);
txBytes = readLongArray(in);
txPackets = readLongArray(in);
operations = readLongArray(in);
bucketCount = bucketStart.length;
totalBytes = in.readLong();
| public NetworkStatsHistory(DataInputStream in)
final int version = in.readInt();
switch (version) {
case VERSION_INIT: {
bucketDuration = in.readLong();
bucketStart = readFullLongArray(in);
rxBytes = readFullLongArray(in);
rxPackets = new long[bucketStart.length];
txBytes = readFullLongArray(in);
txPackets = new long[bucketStart.length];
operations = new long[bucketStart.length];
bucketCount = bucketStart.length;
totalBytes = total(rxBytes) + total(txBytes);
break;
}
case VERSION_ADD_PACKETS:
case VERSION_ADD_ACTIVE: {
bucketDuration = in.readLong();
bucketStart = readVarLongArray(in);
activeTime = (version >= VERSION_ADD_ACTIVE) ? readVarLongArray(in)
: new long[bucketStart.length];
rxBytes = readVarLongArray(in);
rxPackets = readVarLongArray(in);
txBytes = readVarLongArray(in);
txPackets = readVarLongArray(in);
operations = readVarLongArray(in);
bucketCount = bucketStart.length;
totalBytes = total(rxBytes) + total(txBytes);
break;
}
default: {
throw new ProtocolException("unexpected version: " + version);
}
}
if (bucketStart.length != bucketCount || rxBytes.length != bucketCount
|| rxPackets.length != bucketCount || txBytes.length != bucketCount
|| txPackets.length != bucketCount || operations.length != bucketCount) {
throw new ProtocolException("Mismatched history lengths");
}
|
Methods Summary |
---|
private static void | addLong(long[] array, int i, long value)
if (array != null) array[i] += value;
| public int | describeContents()
return 0;
| public void | dump(com.android.internal.util.IndentingPrintWriter pw, boolean fullHistory)
pw.print("NetworkStatsHistory: bucketDuration=");
pw.println(bucketDuration / SECOND_IN_MILLIS);
pw.increaseIndent();
final int start = fullHistory ? 0 : Math.max(0, bucketCount - 32);
if (start > 0) {
pw.print("(omitting "); pw.print(start); pw.println(" buckets)");
}
for (int i = start; i < bucketCount; i++) {
pw.print("st="); pw.print(bucketStart[i] / SECOND_IN_MILLIS);
if (rxBytes != null) { pw.print(" rb="); pw.print(rxBytes[i]); }
if (rxPackets != null) { pw.print(" rp="); pw.print(rxPackets[i]); }
if (txBytes != null) { pw.print(" tb="); pw.print(txBytes[i]); }
if (txPackets != null) { pw.print(" tp="); pw.print(txPackets[i]); }
if (operations != null) { pw.print(" op="); pw.print(operations[i]); }
pw.println();
}
pw.decreaseIndent();
| public void | dumpCheckin(java.io.PrintWriter pw)
pw.print("d,");
pw.print(bucketDuration / SECOND_IN_MILLIS);
pw.println();
for (int i = 0; i < bucketCount; i++) {
pw.print("b,");
pw.print(bucketStart[i] / SECOND_IN_MILLIS); pw.print(',");
if (rxBytes != null) { pw.print(rxBytes[i]); } else { pw.print("*"); } pw.print(',");
if (rxPackets != null) { pw.print(rxPackets[i]); } else { pw.print("*"); } pw.print(',");
if (txBytes != null) { pw.print(txBytes[i]); } else { pw.print("*"); } pw.print(',");
if (txPackets != null) { pw.print(txPackets[i]); } else { pw.print("*"); } pw.print(',");
if (operations != null) { pw.print(operations[i]); } else { pw.print("*"); }
pw.println();
}
| private void | ensureBuckets(long start, long end)Ensure that buckets exist for given time range, creating as needed.
// normalize incoming range to bucket boundaries
start -= start % bucketDuration;
end += (bucketDuration - (end % bucketDuration)) % bucketDuration;
for (long now = start; now < end; now += bucketDuration) {
// try finding existing bucket
final int index = Arrays.binarySearch(bucketStart, 0, bucketCount, now);
if (index < 0) {
// bucket missing, create and insert
insertBucket(~index, now);
}
}
| public int | estimateResizeBuckets(long newBucketDuration)
return (int) (size() * getBucketDuration() / newBucketDuration);
| public void | generateRandom(long start, long end, long bytes)
final Random r = new Random();
final float fractionRx = r.nextFloat();
final long rxBytes = (long) (bytes * fractionRx);
final long txBytes = (long) (bytes * (1 - fractionRx));
final long rxPackets = rxBytes / 1024;
final long txPackets = txBytes / 1024;
final long operations = rxBytes / 2048;
generateRandom(start, end, rxBytes, rxPackets, txBytes, txPackets, operations, r);
| public void | generateRandom(long start, long end, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations, java.util.Random r)
ensureBuckets(start, end);
final NetworkStats.Entry entry = new NetworkStats.Entry(
IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
while (rxBytes > 1024 || rxPackets > 128 || txBytes > 1024 || txPackets > 128
|| operations > 32) {
final long curStart = randomLong(r, start, end);
final long curEnd = curStart + randomLong(r, 0, (end - curStart) / 2);
entry.rxBytes = randomLong(r, 0, rxBytes);
entry.rxPackets = randomLong(r, 0, rxPackets);
entry.txBytes = randomLong(r, 0, txBytes);
entry.txPackets = randomLong(r, 0, txPackets);
entry.operations = randomLong(r, 0, operations);
rxBytes -= entry.rxBytes;
rxPackets -= entry.rxPackets;
txBytes -= entry.txBytes;
txPackets -= entry.txPackets;
operations -= entry.operations;
recordData(curStart, curEnd, entry);
}
| public long | getBucketDuration()
return bucketDuration;
| public long | getEnd()
if (bucketCount > 0) {
return bucketStart[bucketCount - 1] + bucketDuration;
} else {
return Long.MIN_VALUE;
}
| public int | getIndexAfter(long time)Return index of bucket that contains or is immediately after the
requested time.
int index = Arrays.binarySearch(bucketStart, 0, bucketCount, time);
if (index < 0) {
index = ~index;
} else {
index += 1;
}
return MathUtils.constrain(index, 0, bucketCount - 1);
| public int | getIndexBefore(long time)Return index of bucket that contains or is immediately before the
requested time.
int index = Arrays.binarySearch(bucketStart, 0, bucketCount, time);
if (index < 0) {
index = (~index) - 1;
} else {
index -= 1;
}
return MathUtils.constrain(index, 0, bucketCount - 1);
| private static long | getLong(long[] array, int i, long value)
return array != null ? array[i] : value;
| public long | getStart()
if (bucketCount > 0) {
return bucketStart[0];
} else {
return Long.MAX_VALUE;
}
| public long | getTotalBytes()Return total bytes represented by this history.
return totalBytes;
| public android.net.NetworkStatsHistory$Entry | getValues(int i, android.net.NetworkStatsHistory$Entry recycle)Return specific stats entry.
final Entry entry = recycle != null ? recycle : new Entry();
entry.bucketStart = bucketStart[i];
entry.bucketDuration = bucketDuration;
entry.activeTime = getLong(activeTime, i, UNKNOWN);
entry.rxBytes = getLong(rxBytes, i, UNKNOWN);
entry.rxPackets = getLong(rxPackets, i, UNKNOWN);
entry.txBytes = getLong(txBytes, i, UNKNOWN);
entry.txPackets = getLong(txPackets, i, UNKNOWN);
entry.operations = getLong(operations, i, UNKNOWN);
return entry;
| public android.net.NetworkStatsHistory$Entry | getValues(long start, long end, android.net.NetworkStatsHistory$Entry recycle)Return interpolated data usage across the requested range. Interpolates
across buckets, so values may be rounded slightly.
return getValues(start, end, Long.MAX_VALUE, recycle);
| public android.net.NetworkStatsHistory$Entry | getValues(long start, long end, long now, android.net.NetworkStatsHistory$Entry recycle)Return interpolated data usage across the requested range. Interpolates
across buckets, so values may be rounded slightly.
final Entry entry = recycle != null ? recycle : new Entry();
entry.bucketDuration = end - start;
entry.bucketStart = start;
entry.activeTime = activeTime != null ? 0 : UNKNOWN;
entry.rxBytes = rxBytes != null ? 0 : UNKNOWN;
entry.rxPackets = rxPackets != null ? 0 : UNKNOWN;
entry.txBytes = txBytes != null ? 0 : UNKNOWN;
entry.txPackets = txPackets != null ? 0 : UNKNOWN;
entry.operations = operations != null ? 0 : UNKNOWN;
final int startIndex = getIndexAfter(end);
for (int i = startIndex; i >= 0; i--) {
final long curStart = bucketStart[i];
final long curEnd = curStart + bucketDuration;
// bucket is older than request; we're finished
if (curEnd <= start) break;
// bucket is newer than request; keep looking
if (curStart >= end) continue;
// include full value for active buckets, otherwise only fractional
final boolean activeBucket = curStart < now && curEnd > now;
final long overlap;
if (activeBucket) {
overlap = bucketDuration;
} else {
final long overlapEnd = curEnd < end ? curEnd : end;
final long overlapStart = curStart > start ? curStart : start;
overlap = overlapEnd - overlapStart;
}
if (overlap <= 0) continue;
// integer math each time is faster than floating point
if (activeTime != null) entry.activeTime += activeTime[i] * overlap / bucketDuration;
if (rxBytes != null) entry.rxBytes += rxBytes[i] * overlap / bucketDuration;
if (rxPackets != null) entry.rxPackets += rxPackets[i] * overlap / bucketDuration;
if (txBytes != null) entry.txBytes += txBytes[i] * overlap / bucketDuration;
if (txPackets != null) entry.txPackets += txPackets[i] * overlap / bucketDuration;
if (operations != null) entry.operations += operations[i] * overlap / bucketDuration;
}
return entry;
| private void | insertBucket(int index, long start)Insert new bucket at requested index and starting time.
// create more buckets when needed
if (bucketCount >= bucketStart.length) {
final int newLength = Math.max(bucketStart.length, 10) * 3 / 2;
bucketStart = Arrays.copyOf(bucketStart, newLength);
if (activeTime != null) activeTime = Arrays.copyOf(activeTime, newLength);
if (rxBytes != null) rxBytes = Arrays.copyOf(rxBytes, newLength);
if (rxPackets != null) rxPackets = Arrays.copyOf(rxPackets, newLength);
if (txBytes != null) txBytes = Arrays.copyOf(txBytes, newLength);
if (txPackets != null) txPackets = Arrays.copyOf(txPackets, newLength);
if (operations != null) operations = Arrays.copyOf(operations, newLength);
}
// create gap when inserting bucket in middle
if (index < bucketCount) {
final int dstPos = index + 1;
final int length = bucketCount - index;
System.arraycopy(bucketStart, index, bucketStart, dstPos, length);
if (activeTime != null) System.arraycopy(activeTime, index, activeTime, dstPos, length);
if (rxBytes != null) System.arraycopy(rxBytes, index, rxBytes, dstPos, length);
if (rxPackets != null) System.arraycopy(rxPackets, index, rxPackets, dstPos, length);
if (txBytes != null) System.arraycopy(txBytes, index, txBytes, dstPos, length);
if (txPackets != null) System.arraycopy(txPackets, index, txPackets, dstPos, length);
if (operations != null) System.arraycopy(operations, index, operations, dstPos, length);
}
bucketStart[index] = start;
setLong(activeTime, index, 0L);
setLong(rxBytes, index, 0L);
setLong(rxPackets, index, 0L);
setLong(txBytes, index, 0L);
setLong(txPackets, index, 0L);
setLong(operations, index, 0L);
bucketCount++;
| public boolean | intersects(long start, long end)Quickly determine if this history intersects with given window.
final long dataStart = getStart();
final long dataEnd = getEnd();
if (start >= dataStart && start <= dataEnd) return true;
if (end >= dataStart && end <= dataEnd) return true;
if (dataStart >= start && dataStart <= end) return true;
if (dataEnd >= start && dataEnd <= end) return true;
return false;
| public static long | randomLong(java.util.Random r, long start, long end)
return (long) (start + (r.nextFloat() * (end - start)));
| public void | recordData(long start, long end, long rxBytes, long txBytes)Record that data traffic occurred in the given time range. Will
distribute across internal buckets, creating new buckets as needed.
recordData(start, end, new NetworkStats.Entry(
IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, 0L, txBytes, 0L, 0L));
| public void | recordData(long start, long end, NetworkStats.Entry entry)Record that data traffic occurred in the given time range. Will
distribute across internal buckets, creating new buckets as needed.
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.isEmpty()) {
return;
}
// create any buckets needed by this range
ensureBuckets(start, end);
// distribute data usage into buckets
long duration = end - start;
final int startIndex = getIndexAfter(end);
for (int i = startIndex; i >= 0; i--) {
final long curStart = bucketStart[i];
final long curEnd = curStart + bucketDuration;
// bucket is older than record; we're finished
if (curEnd < start) break;
// bucket is newer than record; keep looking
if (curStart > end) continue;
final long overlap = Math.min(curEnd, end) - Math.max(curStart, start);
if (overlap <= 0) continue;
// integer math each time is faster than floating point
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(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;
| public void | recordEntireHistory(android.net.NetworkStatsHistory input)Record an entire {@link NetworkStatsHistory} into this history. Usually
for combining together stats for external reporting.
recordHistory(input, Long.MIN_VALUE, Long.MAX_VALUE);
| public void | recordHistory(android.net.NetworkStatsHistory input, long start, long end)Record given {@link NetworkStatsHistory} into this history, copying only
buckets that atomically occur in the inclusive time range. Doesn't
interpolate across partial buckets.
final NetworkStats.Entry entry = new NetworkStats.Entry(
IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
for (int i = 0; i < input.bucketCount; i++) {
final long bucketStart = input.bucketStart[i];
final long bucketEnd = bucketStart + input.bucketDuration;
// skip when bucket is outside requested range
if (bucketStart < start || bucketEnd > end) continue;
entry.rxBytes = getLong(input.rxBytes, i, 0L);
entry.rxPackets = getLong(input.rxPackets, i, 0L);
entry.txBytes = getLong(input.txBytes, i, 0L);
entry.txPackets = getLong(input.txPackets, i, 0L);
entry.operations = getLong(input.operations, i, 0L);
recordData(bucketStart, bucketEnd, entry);
}
| public void | removeBucketsBefore(long cutoff)Remove buckets older than requested cutoff.
int i;
for (i = 0; i < bucketCount; i++) {
final long curStart = bucketStart[i];
final long curEnd = curStart + bucketDuration;
// cutoff happens before or during this bucket; everything before
// this bucket should be removed.
if (curEnd > cutoff) break;
}
if (i > 0) {
final int length = bucketStart.length;
bucketStart = Arrays.copyOfRange(bucketStart, i, length);
if (activeTime != null) activeTime = Arrays.copyOfRange(activeTime, i, length);
if (rxBytes != null) rxBytes = Arrays.copyOfRange(rxBytes, i, length);
if (rxPackets != null) rxPackets = Arrays.copyOfRange(rxPackets, i, length);
if (txBytes != null) txBytes = Arrays.copyOfRange(txBytes, i, length);
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
}
| private static void | setLong(long[] array, int i, long value)
if (array != null) array[i] = value;
| public int | size()
return bucketCount;
| public java.lang.String | toString()
final CharArrayWriter writer = new CharArrayWriter();
dump(new IndentingPrintWriter(writer, " "), false);
return writer.toString();
| public void | writeToParcel(android.os.Parcel out, int flags)
out.writeLong(bucketDuration);
writeLongArray(out, bucketStart, bucketCount);
writeLongArray(out, activeTime, bucketCount);
writeLongArray(out, rxBytes, bucketCount);
writeLongArray(out, rxPackets, bucketCount);
writeLongArray(out, txBytes, bucketCount);
writeLongArray(out, txPackets, bucketCount);
writeLongArray(out, operations, bucketCount);
out.writeLong(totalBytes);
| public void | writeToStream(java.io.DataOutputStream out)
out.writeInt(VERSION_ADD_ACTIVE);
out.writeLong(bucketDuration);
writeVarLongArray(out, bucketStart, bucketCount);
writeVarLongArray(out, activeTime, bucketCount);
writeVarLongArray(out, rxBytes, bucketCount);
writeVarLongArray(out, rxPackets, bucketCount);
writeVarLongArray(out, txBytes, bucketCount);
writeVarLongArray(out, txPackets, bucketCount);
writeVarLongArray(out, operations, bucketCount);
|
|