blob: b16101fae6d5b3487ed2fc301c5a7c15aa1d43bb [file] [log] [blame]
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.net;
import android.os.Parcel;
import android.os.Parcelable;
import java.io.CharArrayWriter;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
/**
* Collection of historical network statistics, recorded into equally-sized
* "buckets" in time. Internally it stores data in {@code long} series for more
* efficient persistence.
* <p>
* Each bucket is defined by a {@link #bucketStart} timestamp, and lasts for
* {@link #bucketDuration}. Internally assumes that {@link #bucketStart} is
* sorted at all times.
*
* @hide
*/
public class NetworkStatsHistory implements Parcelable {
private static final int VERSION = 1;
/** {@link #uid} value when UID details unavailable. */
public static final int UID_ALL = -1;
// TODO: teach about zigzag encoding to use less disk space
// TODO: teach how to convert between bucket sizes
public final int networkType;
public final String identity;
public final int uid;
public final long bucketDuration;
int bucketCount;
long[] bucketStart;
long[] rx;
long[] tx;
public NetworkStatsHistory(int networkType, String identity, int uid, long bucketDuration) {
this.networkType = networkType;
this.identity = identity;
this.uid = uid;
this.bucketDuration = bucketDuration;
bucketStart = new long[0];
rx = new long[0];
tx = new long[0];
bucketCount = bucketStart.length;
}
public NetworkStatsHistory(Parcel in) {
networkType = in.readInt();
identity = in.readString();
uid = in.readInt();
bucketDuration = in.readLong();
bucketStart = readLongArray(in);
rx = in.createLongArray();
tx = in.createLongArray();
bucketCount = bucketStart.length;
}
/** {@inheritDoc} */
public void writeToParcel(Parcel out, int flags) {
out.writeInt(networkType);
out.writeString(identity);
out.writeInt(uid);
out.writeLong(bucketDuration);
writeLongArray(out, bucketStart, bucketCount);
writeLongArray(out, rx, bucketCount);
writeLongArray(out, tx, bucketCount);
}
public NetworkStatsHistory(DataInputStream in) throws IOException {
final int version = in.readInt();
networkType = in.readInt();
identity = in.readUTF();
uid = in.readInt();
bucketDuration = in.readLong();
bucketStart = readLongArray(in);
rx = readLongArray(in);
tx = readLongArray(in);
bucketCount = bucketStart.length;
}
public void writeToStream(DataOutputStream out) throws IOException {
out.writeInt(VERSION);
out.writeInt(networkType);
out.writeUTF(identity);
out.writeInt(uid);
out.writeLong(bucketDuration);
writeLongArray(out, bucketStart, bucketCount);
writeLongArray(out, rx, bucketCount);
writeLongArray(out, tx, bucketCount);
}
/** {@inheritDoc} */
public int describeContents() {
return 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, long rx, long tx) {
// create any buckets needed by this range
ensureBuckets(start, end);
// distribute data usage into buckets
final long duration = end - start;
for (int i = bucketCount - 1; 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) {
this.rx[i] += rx * overlap / duration;
this.tx[i] += tx * overlap / duration;
}
}
}
/**
* Ensure that buckets exist for given time range, creating as needed.
*/
private void ensureBuckets(long start, long end) {
// 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);
}
}
}
/**
* Insert new bucket at requested index and starting time.
*/
private void insertBucket(int index, long start) {
// create more buckets when needed
if (bucketCount + 1 > bucketStart.length) {
final int newLength = bucketStart.length + 10;
bucketStart = Arrays.copyOf(bucketStart, newLength);
rx = Arrays.copyOf(rx, newLength);
tx = Arrays.copyOf(tx, 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);
System.arraycopy(rx, index, rx, dstPos, length);
System.arraycopy(tx, index, tx, dstPos, length);
}
bucketStart[index] = start;
rx[index] = 0;
tx[index] = 0;
bucketCount++;
}
/**
* Remove buckets older than requested cutoff.
*/
public void removeBucketsBefore(long 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);
rx = Arrays.copyOfRange(rx, i, length);
tx = Arrays.copyOfRange(tx, i, length);
bucketCount -= i;
}
}
public void dump(String prefix, PrintWriter pw) {
// TODO: consider stripping identity when dumping
pw.print(prefix);
pw.print("NetworkStatsHistory: networkType="); pw.print(networkType);
pw.print(" identity="); pw.print(identity);
pw.print(" uid="); pw.println(uid);
for (int i = 0; i < bucketCount; i++) {
pw.print(prefix);
pw.print(" timestamp="); pw.print(bucketStart[i]);
pw.print(" rx="); pw.print(rx[i]);
pw.print(" tx="); pw.println(tx[i]);
}
}
@Override
public String toString() {
final CharArrayWriter writer = new CharArrayWriter();
dump("", new PrintWriter(writer));
return writer.toString();
}
public static final Creator<NetworkStatsHistory> CREATOR = new Creator<NetworkStatsHistory>() {
public NetworkStatsHistory createFromParcel(Parcel in) {
return new NetworkStatsHistory(in);
}
public NetworkStatsHistory[] newArray(int size) {
return new NetworkStatsHistory[size];
}
};
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();
}
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]);
}
}
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();
}
return values;
}
private 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]);
}
}
}