Improve how battery stats collects network stats.

This optimizes the path for battery stats to collect
per-uid network usage.  It now collects wifi and mobile
usage separately, with a path that allows it to recycle
all data structures and filter out stats it isn't
interested in before they come back to java.

This is setting us up for the actual goal, to collect
mobile stats independently each time the mobile radio
goes down, allowing us to distribute mobile radio usage
across uids based on the number of packets they transferred
during a session.

Change-Id: I21a0f517cf087ea5aa8b8dd535e20b46e361a52b
This commit is contained in:
Dianne Hackborn
2014-02-21 16:19:05 -08:00
parent 4181c2f47e
commit fe6e53b48c
3 changed files with 243 additions and 55 deletions

View File

@@ -37,6 +37,7 @@ static jclass gStringClass;
static struct {
jfieldID size;
jfieldID capacity;
jfieldID iface;
jfieldID uid;
jfieldID set;
@@ -49,7 +50,6 @@ static struct {
} gNetworkStatsClassInfo;
struct stats_line {
int32_t idx;
char iface[32];
int32_t uid;
int32_t set;
@@ -60,8 +60,41 @@ struct stats_line {
int64_t txPackets;
};
static jobjectArray get_string_array(JNIEnv* env, jobject obj, jfieldID field, int size, bool grow)
{
if (!grow) {
jobjectArray array = (jobjectArray)env->GetObjectField(obj, field);
if (array != NULL) {
return array;
}
}
return env->NewObjectArray(size, gStringClass, NULL);
}
static jintArray get_int_array(JNIEnv* env, jobject obj, jfieldID field, int size, bool grow)
{
if (!grow) {
jintArray array = (jintArray)env->GetObjectField(obj, field);
if (array != NULL) {
return array;
}
}
return env->NewIntArray(size);
}
static jlongArray get_long_array(JNIEnv* env, jobject obj, jfieldID field, int size, bool grow)
{
if (!grow) {
jlongArray array = (jlongArray)env->GetObjectField(obj, field);
if (array != NULL) {
return array;
}
}
return env->NewLongArray(size);
}
static int readNetworkStatsDetail(JNIEnv* env, jclass clazz, jobject stats,
jstring path, jint limitUid) {
jstring path, jint limitUid, jobjectArray limitIfacesObj, jint limitTag) {
ScopedUtfChars path8(env, path);
if (path8.c_str() == NULL) {
return -1;
@@ -72,50 +105,146 @@ static int readNetworkStatsDetail(JNIEnv* env, jclass clazz, jobject stats,
return -1;
}
Vector<String8> limitIfaces;
if (limitIfacesObj != NULL && env->GetArrayLength(limitIfacesObj) > 0) {
int num = env->GetArrayLength(limitIfacesObj);
limitIfaces.setCapacity(num);
for (int i=0; i<num; i++) {
jstring string = (jstring)env->GetObjectArrayElement(limitIfacesObj, i);
ScopedUtfChars string8(env, string);
if (string8.c_str() != NULL) {
limitIfaces.add(String8(string8.c_str()));
}
}
}
Vector<stats_line> lines;
int lastIdx = 1;
int idx;
char buffer[384];
while (fgets(buffer, sizeof(buffer), fp) != NULL) {
stats_line s;
int64_t rawTag;
if (sscanf(buffer, "%d %31s 0x%llx %u %u %llu %llu %llu %llu", &s.idx,
s.iface, &rawTag, &s.uid, &s.set, &s.rxBytes, &s.rxPackets,
&s.txBytes, &s.txPackets) == 9) {
if (s.idx != lastIdx + 1) {
ALOGE("inconsistent idx=%d after lastIdx=%d", s.idx, lastIdx);
return -1;
char* pos = buffer;
char* endPos;
// First field is the index.
idx = (int)strtol(pos, &endPos, 10);
//ALOGI("Index #%d: %s", idx, buffer);
if (pos == endPos) {
// Skip lines that don't start with in index. In particular,
// this will skip the initial header line.
continue;
}
if (idx != lastIdx + 1) {
ALOGE("inconsistent idx=%d after lastIdx=%d: %s", idx, lastIdx, buffer);
fclose(fp);
return -1;
}
lastIdx = idx;
pos = endPos;
// Skip whitespace.
while (*pos == ' ') {
pos++;
}
// Next field is iface.
int ifaceIdx = 0;
while (*pos != ' ' && *pos != 0 && ifaceIdx < (int)(sizeof(s.iface)-1)) {
s.iface[ifaceIdx] = *pos;
ifaceIdx++;
pos++;
}
if (*pos != ' ') {
ALOGE("bad iface: %s", buffer);
fclose(fp);
return -1;
}
s.iface[ifaceIdx] = 0;
if (limitIfaces.size() > 0) {
// Is this an iface the caller is interested in?
int i = 0;
while (i < (int)limitIfaces.size()) {
if (limitIfaces[i] == s.iface) {
break;
}
i++;
}
if (i >= (int)limitIfaces.size()) {
// Nothing matched; skip this line.
//ALOGI("skipping due to iface: %s", buffer);
continue;
}
}
// Skip whitespace.
while (*pos == ' ') {
pos++;
}
// Next field is tag.
rawTag = strtoll(pos, &endPos, 16);
//ALOGI("Index #%d: %s", idx, buffer);
if (pos == endPos) {
ALOGE("bad tag: %s", pos);
fclose(fp);
return -1;
}
s.tag = rawTag >> 32;
if (limitTag != -1 && s.tag != limitTag) {
//ALOGI("skipping due to tag: %s", buffer);
continue;
}
pos = endPos;
// Skip whitespace.
while (*pos == ' ') {
pos++;
}
// Parse remaining fields.
if (sscanf(pos, "%u %u %llu %llu %llu %llu",
&s.uid, &s.set, &s.rxBytes, &s.rxPackets,
&s.txBytes, &s.txPackets) == 6) {
if (limitUid != -1 && limitUid != s.uid) {
//ALOGI("skipping due to uid: %s", buffer);
continue;
}
lastIdx = s.idx;
s.tag = rawTag >> 32;
lines.push_back(s);
} else {
//ALOGI("skipping due to bad remaining fields: %s", pos);
}
}
if (fclose(fp) != 0) {
ALOGE("Failed to close netstats file");
return -1;
}
int size = lines.size();
bool grow = size > env->GetIntField(stats, gNetworkStatsClassInfo.capacity);
ScopedLocalRef<jobjectArray> iface(env, env->NewObjectArray(size, gStringClass, NULL));
ScopedLocalRef<jobjectArray> iface(env, get_string_array(env, stats,
gNetworkStatsClassInfo.iface, size, grow));
if (iface.get() == NULL) return -1;
ScopedIntArrayRW uid(env, env->NewIntArray(size));
ScopedIntArrayRW uid(env, get_int_array(env, stats,
gNetworkStatsClassInfo.uid, size, grow));
if (uid.get() == NULL) return -1;
ScopedIntArrayRW set(env, env->NewIntArray(size));
ScopedIntArrayRW set(env, get_int_array(env, stats,
gNetworkStatsClassInfo.set, size, grow));
if (set.get() == NULL) return -1;
ScopedIntArrayRW tag(env, env->NewIntArray(size));
ScopedIntArrayRW tag(env, get_int_array(env, stats,
gNetworkStatsClassInfo.tag, size, grow));
if (tag.get() == NULL) return -1;
ScopedLongArrayRW rxBytes(env, env->NewLongArray(size));
ScopedLongArrayRW rxBytes(env, get_long_array(env, stats,
gNetworkStatsClassInfo.rxBytes, size, grow));
if (rxBytes.get() == NULL) return -1;
ScopedLongArrayRW rxPackets(env, env->NewLongArray(size));
ScopedLongArrayRW rxPackets(env, get_long_array(env, stats,
gNetworkStatsClassInfo.rxPackets, size, grow));
if (rxPackets.get() == NULL) return -1;
ScopedLongArrayRW txBytes(env, env->NewLongArray(size));
ScopedLongArrayRW txBytes(env, get_long_array(env, stats,
gNetworkStatsClassInfo.txBytes, size, grow));
if (txBytes.get() == NULL) return -1;
ScopedLongArrayRW txPackets(env, env->NewLongArray(size));
ScopedLongArrayRW txPackets(env, get_long_array(env, stats,
gNetworkStatsClassInfo.txPackets, size, grow));
if (txPackets.get() == NULL) return -1;
ScopedLongArrayRW operations(env, env->NewLongArray(size));
ScopedLongArrayRW operations(env, get_long_array(env, stats,
gNetworkStatsClassInfo.operations, size, grow));
if (operations.get() == NULL) return -1;
for (int i = 0; i < size; i++) {
@@ -132,15 +261,18 @@ static int readNetworkStatsDetail(JNIEnv* env, jclass clazz, jobject stats,
}
env->SetIntField(stats, gNetworkStatsClassInfo.size, size);
env->SetObjectField(stats, gNetworkStatsClassInfo.iface, iface.get());
env->SetObjectField(stats, gNetworkStatsClassInfo.uid, uid.getJavaArray());
env->SetObjectField(stats, gNetworkStatsClassInfo.set, set.getJavaArray());
env->SetObjectField(stats, gNetworkStatsClassInfo.tag, tag.getJavaArray());
env->SetObjectField(stats, gNetworkStatsClassInfo.rxBytes, rxBytes.getJavaArray());
env->SetObjectField(stats, gNetworkStatsClassInfo.rxPackets, rxPackets.getJavaArray());
env->SetObjectField(stats, gNetworkStatsClassInfo.txBytes, txBytes.getJavaArray());
env->SetObjectField(stats, gNetworkStatsClassInfo.txPackets, txPackets.getJavaArray());
env->SetObjectField(stats, gNetworkStatsClassInfo.operations, operations.getJavaArray());
if (grow) {
env->SetIntField(stats, gNetworkStatsClassInfo.capacity, size);
env->SetObjectField(stats, gNetworkStatsClassInfo.iface, iface.get());
env->SetObjectField(stats, gNetworkStatsClassInfo.uid, uid.getJavaArray());
env->SetObjectField(stats, gNetworkStatsClassInfo.set, set.getJavaArray());
env->SetObjectField(stats, gNetworkStatsClassInfo.tag, tag.getJavaArray());
env->SetObjectField(stats, gNetworkStatsClassInfo.rxBytes, rxBytes.getJavaArray());
env->SetObjectField(stats, gNetworkStatsClassInfo.rxPackets, rxPackets.getJavaArray());
env->SetObjectField(stats, gNetworkStatsClassInfo.txBytes, txBytes.getJavaArray());
env->SetObjectField(stats, gNetworkStatsClassInfo.txPackets, txPackets.getJavaArray());
env->SetObjectField(stats, gNetworkStatsClassInfo.operations, operations.getJavaArray());
}
return 0;
}
@@ -157,7 +289,7 @@ static jclass findClass(JNIEnv* env, const char* name) {
static JNINativeMethod gMethods[] = {
{ "nativeReadNetworkStatsDetail",
"(Landroid/net/NetworkStats;Ljava/lang/String;I)I",
"(Landroid/net/NetworkStats;Ljava/lang/String;I[Ljava/lang/String;I)I",
(void*) readNetworkStatsDetail }
};
@@ -170,6 +302,7 @@ int register_com_android_internal_net_NetworkStatsFactory(JNIEnv* env) {
jclass clazz = env->FindClass("android/net/NetworkStats");
gNetworkStatsClassInfo.size = env->GetFieldID(clazz, "size", "I");
gNetworkStatsClassInfo.capacity = env->GetFieldID(clazz, "capacity", "I");
gNetworkStatsClassInfo.iface = env->GetFieldID(clazz, "iface", "[Ljava/lang/String;");
gNetworkStatsClassInfo.uid = env->GetFieldID(clazz, "uid", "[I");
gNetworkStatsClassInfo.set = env->GetFieldID(clazz, "set", "[I");