diff options
author | Jeff Sharkey <jsharkey@android.com> | 2013-01-14 16:48:51 -0800 |
---|---|---|
committer | Jeff Sharkey <jsharkey@android.com> | 2013-02-13 17:40:49 -0800 |
commit | 9a2c2a6da90abbcc9a064c20e93ed885651f4ae1 (patch) | |
tree | 424065d447332bcbc2f95782abcedf897d9a22a2 /core | |
parent | 336fcac31ddccc7e7a6773d03e7cb17967ebb898 (diff) | |
download | frameworks_base-9a2c2a6da90abbcc9a064c20e93ed885651f4ae1.zip frameworks_base-9a2c2a6da90abbcc9a064c20e93ed885651f4ae1.tar.gz frameworks_base-9a2c2a6da90abbcc9a064c20e93ed885651f4ae1.tar.bz2 |
Parse network stats using native code.
Switch to parsing detailed network stats with native code, which
is 71% faster than ProcFileReader.
Change-Id: I2525aaee74d227ce187ba3a74dd08a2b06514deb
Diffstat (limited to 'core')
-rw-r--r-- | core/java/android/net/NetworkStats.java | 12 | ||||
-rw-r--r-- | core/java/com/android/internal/net/NetworkStatsFactory.java | 84 | ||||
-rw-r--r-- | core/java/com/android/internal/os/BatteryStatsImpl.java | 4 | ||||
-rw-r--r-- | core/jni/Android.mk | 5 | ||||
-rw-r--r-- | core/jni/AndroidRuntime.cpp | 2 | ||||
-rw-r--r-- | core/jni/com_android_internal_net_NetworkStatsFactory.cpp | 187 | ||||
-rw-r--r-- | core/tests/benchmarks/src/com/android/internal/net/NetworkStatsFactoryBenchmark.java | 54 |
7 files changed, 321 insertions, 27 deletions
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java index c757605..9cb904d 100644 --- a/core/java/android/net/NetworkStats.java +++ b/core/java/android/net/NetworkStats.java @@ -135,6 +135,18 @@ public class NetworkStats implements Parcelable { builder.append(" operations=").append(operations); 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) { diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java index c517a68..8282d23 100644 --- a/core/java/com/android/internal/net/NetworkStatsFactory.java +++ b/core/java/com/android/internal/net/NetworkStatsFactory.java @@ -31,6 +31,7 @@ import com.android.internal.util.ProcFileReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.net.ProtocolException; import libcore.io.IoUtils; @@ -41,7 +42,8 @@ import libcore.io.IoUtils; public class 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}. */ private final File mStatsXtIfaceAll; @@ -69,7 +71,7 @@ public class NetworkStatsFactory { * * @throws IllegalStateException when problem parsing stats. */ - public NetworkStats readNetworkStatsSummaryDev() throws IllegalStateException { + public NetworkStats readNetworkStatsSummaryDev() throws IOException { final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads(); final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6); @@ -105,11 +107,9 @@ public class NetworkStatsFactory { reader.finishLine(); } } catch (NullPointerException e) { - throw new IllegalStateException("problem parsing stats: " + e); + throw new ProtocolException("problem parsing stats", e); } catch (NumberFormatException e) { - throw new IllegalStateException("problem parsing stats: " + e); - } catch (IOException e) { - throw new IllegalStateException("problem parsing stats: " + e); + throw new ProtocolException("problem parsing stats", e); } finally { IoUtils.closeQuietly(reader); StrictMode.setThreadPolicy(savedPolicy); @@ -124,7 +124,7 @@ public class NetworkStatsFactory { * * @throws IllegalStateException when problem parsing stats. */ - public NetworkStats readNetworkStatsSummaryXt() throws IllegalStateException { + public NetworkStats readNetworkStatsSummaryXt() throws IOException { final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads(); // return null when kernel doesn't support @@ -154,11 +154,9 @@ public class NetworkStatsFactory { reader.finishLine(); } } catch (NullPointerException e) { - throw new IllegalStateException("problem parsing stats: " + e); + throw new ProtocolException("problem parsing stats", e); } catch (NumberFormatException e) { - throw new IllegalStateException("problem parsing stats: " + e); - } catch (IOException e) { - throw new IllegalStateException("problem parsing stats: " + e); + throw new ProtocolException("problem parsing stats", e); } finally { IoUtils.closeQuietly(reader); StrictMode.setThreadPolicy(savedPolicy); @@ -166,17 +164,33 @@ public class NetworkStatsFactory { return stats; } - public NetworkStats readNetworkStatsDetail() { + public NetworkStats readNetworkStatsDetail() throws IOException { 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 - * monotonically increase since device boot. - * - * @throws IllegalStateException when problem parsing stats. + * Parse and return {@link NetworkStats} with UID-level details. Values are + * expected to monotonically increase since device boot. */ - 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 NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 24); @@ -188,13 +202,13 @@ public class NetworkStatsFactory { ProcFileReader reader = null; try { // open and consume header line - reader = new ProcFileReader(new FileInputStream(mStatsXtUid)); + reader = new ProcFileReader(new FileInputStream(detailPath)); reader.finishLine(); while (reader.hasMoreData()) { idx = reader.nextInt(); if (idx != lastIdx + 1) { - throw new IllegalStateException( + throw new ProtocolException( "inconsistent idx=" + idx + " after lastIdx=" + lastIdx); } lastIdx = idx; @@ -215,11 +229,9 @@ public class NetworkStatsFactory { reader.finishLine(); } } catch (NullPointerException e) { - throw new IllegalStateException("problem parsing idx " + idx, e); + throw new ProtocolException("problem parsing idx " + idx, e); } catch (NumberFormatException e) { - throw new IllegalStateException("problem parsing idx " + idx, e); - } catch (IOException e) { - throw new IllegalStateException("problem parsing idx " + idx, e); + throw new ProtocolException("problem parsing idx " + idx, e); } finally { IoUtils.closeQuietly(reader); StrictMode.setThreadPolicy(savedPolicy); @@ -227,4 +239,30 @@ public class NetworkStatsFactory { 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); } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 4d35a6b..04b9884 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -5956,7 +5956,7 @@ public final class BatteryStatsImpl extends BatteryStats { if (SystemProperties.getBoolean(PROP_QTAGUID_ENABLED, false)) { try { mNetworkSummaryCache = mNetworkStatsFactory.readNetworkStatsSummaryDev(); - } catch (IllegalStateException e) { + } catch (IOException e) { Log.wtf(TAG, "problem reading network stats", e); } } @@ -5980,7 +5980,7 @@ public final class BatteryStatsImpl extends BatteryStats { try { mNetworkDetailCache = mNetworkStatsFactory .readNetworkStatsDetail().groupedByUid(); - } catch (IllegalStateException e) { + } catch (IOException e) { Log.wtf(TAG, "problem reading network stats", e); } } diff --git a/core/jni/Android.mk b/core/jni/Android.mk index d705024..5337329 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -145,7 +145,8 @@ LOCAL_SRC_FILES:= \ android_app_backup_FullBackup.cpp \ android_content_res_ObbScanner.cpp \ android_content_res_Configuration.cpp \ - android_animation_PropertyValuesHolder.cpp + android_animation_PropertyValuesHolder.cpp \ + com_android_internal_net_NetworkStatsFactory.cpp LOCAL_C_INCLUDES += \ $(JNI_H_INCLUDE) \ @@ -155,7 +156,7 @@ LOCAL_C_INCLUDES += \ $(call include-path-for, bluedroid) \ $(call include-path-for, libhardware)/hardware \ $(call include-path-for, libhardware_legacy)/hardware_legacy \ - $(TOP)/frameworks/av/include \ + $(TOP)/frameworks/av/include \ external/skia/include/core \ external/skia/include/effects \ external/skia/include/images \ diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 94324f8..aa59b43 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -173,6 +173,7 @@ extern int register_android_content_res_ObbScanner(JNIEnv* env); extern int register_android_content_res_Configuration(JNIEnv* env); extern int register_android_animation_PropertyValuesHolder(JNIEnv *env); extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env); +extern int register_com_android_internal_net_NetworkStatsFactory(JNIEnv *env); static AndroidRuntime* gCurRuntime = NULL; @@ -1204,6 +1205,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_animation_PropertyValuesHolder), REG_JNI(register_com_android_internal_content_NativeLibraryHelper), + REG_JNI(register_com_android_internal_net_NetworkStatsFactory), }; /* diff --git a/core/jni/com_android_internal_net_NetworkStatsFactory.cpp b/core/jni/com_android_internal_net_NetworkStatsFactory.cpp new file mode 100644 index 0000000..0906593 --- /dev/null +++ b/core/jni/com_android_internal_net_NetworkStatsFactory.cpp @@ -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; +} + +} diff --git a/core/tests/benchmarks/src/com/android/internal/net/NetworkStatsFactoryBenchmark.java b/core/tests/benchmarks/src/com/android/internal/net/NetworkStatsFactoryBenchmark.java new file mode 100644 index 0000000..2174be5 --- /dev/null +++ b/core/tests/benchmarks/src/com/android/internal/net/NetworkStatsFactoryBenchmark.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2012 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 com.android.internal.net; + +import android.net.NetworkStats; +import android.os.SystemClock; + +import com.google.caliper.SimpleBenchmark; + +import java.io.File; + +public class NetworkStatsFactoryBenchmark extends SimpleBenchmark { + private File mStats; + + // TODO: consider staging stats file with different number of rows + + @Override + protected void setUp() { + mStats = new File("/proc/net/xt_qtaguid/stats"); + } + + @Override + protected void tearDown() { + mStats = null; + } + + public void timeReadNetworkStatsDetailJava(int reps) throws Exception { + for (int i = 0; i < reps; i++) { + NetworkStatsFactory.javaReadNetworkStatsDetail(mStats, NetworkStats.UID_ALL); + } + } + + public void timeReadNetworkStatsDetailNative(int reps) { + for (int i = 0; i < reps; i++) { + final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0); + NetworkStatsFactory.nativeReadNetworkStatsDetail( + stats, mStats.getAbsolutePath(), NetworkStats.UID_ALL); + } + } +} |