Parse network stats using native code.

Switch to parsing detailed network stats with native code, which
is 71% faster than ProcFileReader.

Change-Id: I2525aaee74d227ce187ba3a74dd08a2b06514deb
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 @@
             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 java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
+import java.net.ProtocolException;
 
 import libcore.io.IoUtils;
 
@@ -41,7 +42,8 @@
 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 @@
      *
      * @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 @@
                 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 @@
      *
      * @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 @@
                 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 @@
         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 @@
         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 @@
                 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 @@
 
         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..04b98841 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -5956,7 +5956,7 @@
                 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 @@
                     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 @@
 	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 @@
 	$(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_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 @@
 
     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);
+        }
+    }
+}
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index 29e4c43..25ed27a 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -1120,19 +1120,31 @@
     @Override
     public NetworkStats getNetworkStatsSummaryDev() {
         mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-        return mStatsFactory.readNetworkStatsSummaryDev();
+        try {
+            return mStatsFactory.readNetworkStatsSummaryDev();
+        } catch (IOException e) {
+            throw new IllegalStateException(e);
+        }
     }
 
     @Override
     public NetworkStats getNetworkStatsSummaryXt() {
         mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-        return mStatsFactory.readNetworkStatsSummaryXt();
+        try {
+            return mStatsFactory.readNetworkStatsSummaryXt();
+        } catch (IOException e) {
+            throw new IllegalStateException(e);
+        }
     }
 
     @Override
     public NetworkStats getNetworkStatsDetail() {
         mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-        return mStatsFactory.readNetworkStatsDetail(UID_ALL);
+        try {
+            return mStatsFactory.readNetworkStatsDetail(UID_ALL);
+        } catch (IOException e) {
+            throw new IllegalStateException(e);
+        }
     }
 
     @Override
@@ -1289,7 +1301,11 @@
     @Override
     public NetworkStats getNetworkStatsUidDetail(int uid) {
         mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-        return mStatsFactory.readNetworkStatsDetail(uid);
+        try {
+            return mStatsFactory.readNetworkStatsDetail(uid);
+        } catch (IOException e) {
+            throw new IllegalStateException(e);
+        }
     }
 
     @Override