Parse network stats using native code.
Switch to parsing detailed network stats with native code, which is 71% faster than ProcFileReader. Change-Id: I2525aaee74d227ce187ba3a74dd08a2b06514deb
This commit is contained in:
@@ -135,6 +135,18 @@ public class NetworkStats implements Parcelable {
|
|||||||
builder.append(" operations=").append(operations);
|
builder.append(" operations=").append(operations);
|
||||||
return builder.toString();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o instanceof Entry) {
|
||||||
|
final Entry e = (Entry) o;
|
||||||
|
return uid == e.uid && set == e.set && tag == e.tag && rxBytes == e.rxBytes
|
||||||
|
&& rxPackets == e.rxPackets && txBytes == e.txBytes
|
||||||
|
&& txPackets == e.txPackets && operations == e.operations
|
||||||
|
&& iface.equals(e.iface);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public NetworkStats(long elapsedRealtime, int initialSize) {
|
public NetworkStats(long elapsedRealtime, int initialSize) {
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import com.android.internal.util.ProcFileReader;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.ProtocolException;
|
||||||
|
|
||||||
import libcore.io.IoUtils;
|
import libcore.io.IoUtils;
|
||||||
|
|
||||||
@@ -41,7 +42,8 @@ import libcore.io.IoUtils;
|
|||||||
public class NetworkStatsFactory {
|
public class NetworkStatsFactory {
|
||||||
private static final String TAG = "NetworkStatsFactory";
|
private static final String TAG = "NetworkStatsFactory";
|
||||||
|
|
||||||
// TODO: consider moving parsing to native code
|
private static final boolean USE_NATIVE_PARSING = true;
|
||||||
|
private static final boolean SANITY_CHECK_NATIVE = false;
|
||||||
|
|
||||||
/** Path to {@code /proc/net/xt_qtaguid/iface_stat_all}. */
|
/** Path to {@code /proc/net/xt_qtaguid/iface_stat_all}. */
|
||||||
private final File mStatsXtIfaceAll;
|
private final File mStatsXtIfaceAll;
|
||||||
@@ -69,7 +71,7 @@ public class NetworkStatsFactory {
|
|||||||
*
|
*
|
||||||
* @throws IllegalStateException when problem parsing stats.
|
* @throws IllegalStateException when problem parsing stats.
|
||||||
*/
|
*/
|
||||||
public NetworkStats readNetworkStatsSummaryDev() throws IllegalStateException {
|
public NetworkStats readNetworkStatsSummaryDev() throws IOException {
|
||||||
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
|
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
|
||||||
|
|
||||||
final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6);
|
final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6);
|
||||||
@@ -105,11 +107,9 @@ public class NetworkStatsFactory {
|
|||||||
reader.finishLine();
|
reader.finishLine();
|
||||||
}
|
}
|
||||||
} catch (NullPointerException e) {
|
} catch (NullPointerException e) {
|
||||||
throw new IllegalStateException("problem parsing stats: " + e);
|
throw new ProtocolException("problem parsing stats", e);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
throw new IllegalStateException("problem parsing stats: " + e);
|
throw new ProtocolException("problem parsing stats", e);
|
||||||
} catch (IOException e) {
|
|
||||||
throw new IllegalStateException("problem parsing stats: " + e);
|
|
||||||
} finally {
|
} finally {
|
||||||
IoUtils.closeQuietly(reader);
|
IoUtils.closeQuietly(reader);
|
||||||
StrictMode.setThreadPolicy(savedPolicy);
|
StrictMode.setThreadPolicy(savedPolicy);
|
||||||
@@ -124,7 +124,7 @@ public class NetworkStatsFactory {
|
|||||||
*
|
*
|
||||||
* @throws IllegalStateException when problem parsing stats.
|
* @throws IllegalStateException when problem parsing stats.
|
||||||
*/
|
*/
|
||||||
public NetworkStats readNetworkStatsSummaryXt() throws IllegalStateException {
|
public NetworkStats readNetworkStatsSummaryXt() throws IOException {
|
||||||
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
|
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
|
||||||
|
|
||||||
// return null when kernel doesn't support
|
// return null when kernel doesn't support
|
||||||
@@ -154,11 +154,9 @@ public class NetworkStatsFactory {
|
|||||||
reader.finishLine();
|
reader.finishLine();
|
||||||
}
|
}
|
||||||
} catch (NullPointerException e) {
|
} catch (NullPointerException e) {
|
||||||
throw new IllegalStateException("problem parsing stats: " + e);
|
throw new ProtocolException("problem parsing stats", e);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
throw new IllegalStateException("problem parsing stats: " + e);
|
throw new ProtocolException("problem parsing stats", e);
|
||||||
} catch (IOException e) {
|
|
||||||
throw new IllegalStateException("problem parsing stats: " + e);
|
|
||||||
} finally {
|
} finally {
|
||||||
IoUtils.closeQuietly(reader);
|
IoUtils.closeQuietly(reader);
|
||||||
StrictMode.setThreadPolicy(savedPolicy);
|
StrictMode.setThreadPolicy(savedPolicy);
|
||||||
@@ -166,17 +164,33 @@ public class NetworkStatsFactory {
|
|||||||
return stats;
|
return stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
public NetworkStats readNetworkStatsDetail() {
|
public NetworkStats readNetworkStatsDetail() throws IOException {
|
||||||
return readNetworkStatsDetail(UID_ALL);
|
return readNetworkStatsDetail(UID_ALL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public NetworkStats readNetworkStatsDetail(int limitUid) throws IOException {
|
||||||
|
if (USE_NATIVE_PARSING) {
|
||||||
|
final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0);
|
||||||
|
if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), limitUid) != 0) {
|
||||||
|
throw new IOException("Failed to parse network stats");
|
||||||
|
}
|
||||||
|
if (SANITY_CHECK_NATIVE) {
|
||||||
|
final NetworkStats javaStats = javaReadNetworkStatsDetail(mStatsXtUid, limitUid);
|
||||||
|
assertEquals(javaStats, stats);
|
||||||
|
}
|
||||||
|
return stats;
|
||||||
|
} else {
|
||||||
|
return javaReadNetworkStatsDetail(mStatsXtUid, limitUid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse and return {@link NetworkStats} with UID-level details. Values
|
* Parse and return {@link NetworkStats} with UID-level details. Values are
|
||||||
* monotonically increase since device boot.
|
* expected to monotonically increase since device boot.
|
||||||
*
|
|
||||||
* @throws IllegalStateException when problem parsing stats.
|
|
||||||
*/
|
*/
|
||||||
public NetworkStats readNetworkStatsDetail(int limitUid) throws IllegalStateException {
|
@VisibleForTesting
|
||||||
|
public static NetworkStats javaReadNetworkStatsDetail(File detailPath, int limitUid)
|
||||||
|
throws IOException {
|
||||||
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
|
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
|
||||||
|
|
||||||
final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 24);
|
final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 24);
|
||||||
@@ -188,13 +202,13 @@ public class NetworkStatsFactory {
|
|||||||
ProcFileReader reader = null;
|
ProcFileReader reader = null;
|
||||||
try {
|
try {
|
||||||
// open and consume header line
|
// open and consume header line
|
||||||
reader = new ProcFileReader(new FileInputStream(mStatsXtUid));
|
reader = new ProcFileReader(new FileInputStream(detailPath));
|
||||||
reader.finishLine();
|
reader.finishLine();
|
||||||
|
|
||||||
while (reader.hasMoreData()) {
|
while (reader.hasMoreData()) {
|
||||||
idx = reader.nextInt();
|
idx = reader.nextInt();
|
||||||
if (idx != lastIdx + 1) {
|
if (idx != lastIdx + 1) {
|
||||||
throw new IllegalStateException(
|
throw new ProtocolException(
|
||||||
"inconsistent idx=" + idx + " after lastIdx=" + lastIdx);
|
"inconsistent idx=" + idx + " after lastIdx=" + lastIdx);
|
||||||
}
|
}
|
||||||
lastIdx = idx;
|
lastIdx = idx;
|
||||||
@@ -215,11 +229,9 @@ public class NetworkStatsFactory {
|
|||||||
reader.finishLine();
|
reader.finishLine();
|
||||||
}
|
}
|
||||||
} catch (NullPointerException e) {
|
} catch (NullPointerException e) {
|
||||||
throw new IllegalStateException("problem parsing idx " + idx, e);
|
throw new ProtocolException("problem parsing idx " + idx, e);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
throw new IllegalStateException("problem parsing idx " + idx, e);
|
throw new ProtocolException("problem parsing idx " + idx, e);
|
||||||
} catch (IOException e) {
|
|
||||||
throw new IllegalStateException("problem parsing idx " + idx, e);
|
|
||||||
} finally {
|
} finally {
|
||||||
IoUtils.closeQuietly(reader);
|
IoUtils.closeQuietly(reader);
|
||||||
StrictMode.setThreadPolicy(savedPolicy);
|
StrictMode.setThreadPolicy(savedPolicy);
|
||||||
@@ -227,4 +239,30 @@ public class NetworkStatsFactory {
|
|||||||
|
|
||||||
return stats;
|
return stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void assertEquals(NetworkStats expected, NetworkStats actual) {
|
||||||
|
if (expected.size() != actual.size()) {
|
||||||
|
throw new AssertionError(
|
||||||
|
"Expected size " + expected.size() + ", actual size " + actual.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkStats.Entry expectedRow = null;
|
||||||
|
NetworkStats.Entry actualRow = null;
|
||||||
|
for (int i = 0; i < expected.size(); i++) {
|
||||||
|
expectedRow = expected.getValues(i, expectedRow);
|
||||||
|
actualRow = actual.getValues(i, actualRow);
|
||||||
|
if (!expectedRow.equals(actualRow)) {
|
||||||
|
throw new AssertionError(
|
||||||
|
"Expected row " + i + ": " + expectedRow + ", actual row " + actualRow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse statistics from file into given {@link NetworkStats} object. Values
|
||||||
|
* are expected to monotonically increase since device boot.
|
||||||
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
|
public static native int nativeReadNetworkStatsDetail(
|
||||||
|
NetworkStats stats, String path, int limitUid);
|
||||||
}
|
}
|
||||||
|
|||||||
187
core/jni/com_android_internal_net_NetworkStatsFactory.cpp
Normal file
187
core/jni/com_android_internal_net_NetworkStatsFactory.cpp
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2013 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define LOG_TAG "NetworkStats"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#include <android_runtime/AndroidRuntime.h>
|
||||||
|
#include <cutils/logger.h>
|
||||||
|
#include <jni.h>
|
||||||
|
|
||||||
|
#include <ScopedUtfChars.h>
|
||||||
|
#include <ScopedLocalRef.h>
|
||||||
|
#include <ScopedPrimitiveArray.h>
|
||||||
|
|
||||||
|
#include <utils/Log.h>
|
||||||
|
#include <utils/misc.h>
|
||||||
|
#include <utils/Vector.h>
|
||||||
|
|
||||||
|
namespace android {
|
||||||
|
|
||||||
|
static jclass gStringClass;
|
||||||
|
|
||||||
|
static struct {
|
||||||
|
jfieldID size;
|
||||||
|
jfieldID iface;
|
||||||
|
jfieldID uid;
|
||||||
|
jfieldID set;
|
||||||
|
jfieldID tag;
|
||||||
|
jfieldID rxBytes;
|
||||||
|
jfieldID rxPackets;
|
||||||
|
jfieldID txBytes;
|
||||||
|
jfieldID txPackets;
|
||||||
|
jfieldID operations;
|
||||||
|
} gNetworkStatsClassInfo;
|
||||||
|
|
||||||
|
struct stats_line {
|
||||||
|
int32_t idx;
|
||||||
|
char iface[32];
|
||||||
|
int32_t uid;
|
||||||
|
int32_t set;
|
||||||
|
int32_t tag;
|
||||||
|
int64_t rxBytes;
|
||||||
|
int64_t rxPackets;
|
||||||
|
int64_t txBytes;
|
||||||
|
int64_t txPackets;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int readNetworkStatsDetail(JNIEnv* env, jclass clazz, jobject stats,
|
||||||
|
jstring path, jint limitUid) {
|
||||||
|
ScopedUtfChars path8(env, path);
|
||||||
|
if (path8.c_str() == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE *fp = fopen(path8.c_str(), "r");
|
||||||
|
if (fp == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector<stats_line> lines;
|
||||||
|
|
||||||
|
int lastIdx = 1;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
lastIdx = s.idx;
|
||||||
|
|
||||||
|
s.tag = rawTag >> 32;
|
||||||
|
lines.push_back(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fclose(fp) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int size = lines.size();
|
||||||
|
|
||||||
|
ScopedLocalRef<jobjectArray> iface(env, env->NewObjectArray(size, gStringClass, NULL));
|
||||||
|
if (iface.get() == NULL) return -1;
|
||||||
|
ScopedIntArrayRW uid(env, env->NewIntArray(size));
|
||||||
|
if (uid.get() == NULL) return -1;
|
||||||
|
ScopedIntArrayRW set(env, env->NewIntArray(size));
|
||||||
|
if (set.get() == NULL) return -1;
|
||||||
|
ScopedIntArrayRW tag(env, env->NewIntArray(size));
|
||||||
|
if (tag.get() == NULL) return -1;
|
||||||
|
ScopedLongArrayRW rxBytes(env, env->NewLongArray(size));
|
||||||
|
if (rxBytes.get() == NULL) return -1;
|
||||||
|
ScopedLongArrayRW rxPackets(env, env->NewLongArray(size));
|
||||||
|
if (rxPackets.get() == NULL) return -1;
|
||||||
|
ScopedLongArrayRW txBytes(env, env->NewLongArray(size));
|
||||||
|
if (txBytes.get() == NULL) return -1;
|
||||||
|
ScopedLongArrayRW txPackets(env, env->NewLongArray(size));
|
||||||
|
if (txPackets.get() == NULL) return -1;
|
||||||
|
ScopedLongArrayRW operations(env, env->NewLongArray(size));
|
||||||
|
if (operations.get() == NULL) return -1;
|
||||||
|
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
ScopedLocalRef<jstring> ifaceString(env, env->NewStringUTF(lines[i].iface));
|
||||||
|
env->SetObjectArrayElement(iface.get(), i, ifaceString.get());
|
||||||
|
|
||||||
|
uid[i] = lines[i].uid;
|
||||||
|
set[i] = lines[i].set;
|
||||||
|
tag[i] = lines[i].tag;
|
||||||
|
rxBytes[i] = lines[i].rxBytes;
|
||||||
|
rxPackets[i] = lines[i].rxPackets;
|
||||||
|
txBytes[i] = lines[i].txBytes;
|
||||||
|
txPackets[i] = lines[i].txPackets;
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static jclass findClass(JNIEnv* env, const char* name) {
|
||||||
|
ScopedLocalRef<jclass> localClass(env, env->FindClass(name));
|
||||||
|
jclass result = reinterpret_cast<jclass>(env->NewGlobalRef(localClass.get()));
|
||||||
|
if (result == NULL) {
|
||||||
|
ALOGE("failed to find class '%s'", name);
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static JNINativeMethod gMethods[] = {
|
||||||
|
{ "nativeReadNetworkStatsDetail",
|
||||||
|
"(Landroid/net/NetworkStats;Ljava/lang/String;I)I",
|
||||||
|
(void*) readNetworkStatsDetail }
|
||||||
|
};
|
||||||
|
|
||||||
|
int register_com_android_internal_net_NetworkStatsFactory(JNIEnv* env) {
|
||||||
|
int err = AndroidRuntime::registerNativeMethods(env,
|
||||||
|
"com/android/internal/net/NetworkStatsFactory", gMethods,
|
||||||
|
NELEM(gMethods));
|
||||||
|
|
||||||
|
gStringClass = findClass(env, "java/lang/String");
|
||||||
|
|
||||||
|
jclass clazz = env->FindClass("android/net/NetworkStats");
|
||||||
|
gNetworkStatsClassInfo.size = env->GetFieldID(clazz, "size", "I");
|
||||||
|
gNetworkStatsClassInfo.iface = env->GetFieldID(clazz, "iface", "[Ljava/lang/String;");
|
||||||
|
gNetworkStatsClassInfo.uid = env->GetFieldID(clazz, "uid", "[I");
|
||||||
|
gNetworkStatsClassInfo.set = env->GetFieldID(clazz, "set", "[I");
|
||||||
|
gNetworkStatsClassInfo.tag = env->GetFieldID(clazz, "tag", "[I");
|
||||||
|
gNetworkStatsClassInfo.rxBytes = env->GetFieldID(clazz, "rxBytes", "[J");
|
||||||
|
gNetworkStatsClassInfo.rxPackets = env->GetFieldID(clazz, "rxPackets", "[J");
|
||||||
|
gNetworkStatsClassInfo.txBytes = env->GetFieldID(clazz, "txBytes", "[J");
|
||||||
|
gNetworkStatsClassInfo.txPackets = env->GetFieldID(clazz, "txPackets", "[J");
|
||||||
|
gNetworkStatsClassInfo.operations = env->GetFieldID(clazz, "operations", "[J");
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user