Avoid excessive allocation churn in network stats.

Getting the network stats required making 3 full copies of all the
network stats since boot. Reorganize the code so we only have to
allocate incremental network stats objects.

Flag: EXEMPT bugfix
Bug: 399154099
Test: atest ConnectivityCoverageTests
Test: atest NetworkStatsIntegrationTest
Change-Id: Id531c4ffd6e0d127b8aaac38bda6a89a3d700c64
diff --git a/framework-t/src/android/net/NetworkStats.java b/framework-t/src/android/net/NetworkStats.java
index a2c4fc3..5b7dddb 100644
--- a/framework-t/src/android/net/NetworkStats.java
+++ b/framework-t/src/android/net/NetworkStats.java
@@ -494,6 +494,21 @@
             return this;
         }
 
+        /**
+         * Checks if this entry matches the given filter parameters.
+         * @param uid UID to filter for, or {@link #UID_ALL}.
+         * @param ifaces Interfaces to filter for, or {@link #INTERFACES_ALL}.
+         * @param tag Tag to filter for, or {@link #TAG_ALL}.
+         *
+         * @return true if this entry matches the given filter parameters.
+         * @hide
+         */
+        public boolean matches(int uid, String[] ifaces, int tag) {
+            return (uid == UID_ALL || uid == this.uid)
+                && (tag == TAG_ALL || tag == this.tag)
+                && (ifaces == INTERFACES_ALL || CollectionUtils.contains(ifaces, this.iface));
+        }
+
         @Override
         public String toString() {
             final StringBuilder builder = new StringBuilder();
@@ -1379,10 +1394,7 @@
         if (limitUid == UID_ALL && limitTag == TAG_ALL && limitIfaces == INTERFACES_ALL) {
             return;
         }
-        filter(e -> (limitUid == UID_ALL || limitUid == e.uid)
-                && (limitTag == TAG_ALL || limitTag == e.tag)
-                && (limitIfaces == INTERFACES_ALL
-                    || CollectionUtils.contains(limitIfaces, e.iface)));
+        filter(e -> e.matches(limitUid, limitIfaces, limitTag));
     }
 
     /**
@@ -1410,6 +1422,34 @@
         size = nextOutputEntry;
     }
 
+    /**
+     * Make a filtered copy of the network stats.
+     *
+     * @param limitUid UID to filter for, or {@link #UID_ALL}.
+     * @param limitIfaces Interfaces to filter for, or {@link #INTERFACES_ALL}.
+     * @param limitTag Tag to filter for, or {@link #TAG_ALL}.
+     * @hide
+     */
+    public NetworkStats filteredClone(int limitUid, String[] limitIfaces, int limitTag) {
+        NetworkStats.Entry e = null;
+        int filteredSize = 0;
+        for (int i = 0; i < size; i++) {
+            e = getValues(i, e);
+            if (e.matches(limitUid, limitIfaces, limitTag)) {
+                filteredSize++;
+            }
+        }
+
+        final NetworkStats clone = new NetworkStats(elapsedRealtime, filteredSize);
+        for (int i = 0; i < size; i++) {
+            e = getValues(i, e);
+            if (e.matches(limitUid, limitIfaces, limitTag)) {
+                clone.insertEntry(e);
+            }
+        }
+        return clone;
+    }
+
     /** @hide */
     public void dump(String prefix, PrintWriter pw) {
         pw.print(prefix);
diff --git a/service-t/src/com/android/server/net/NetworkStatsFactory.java b/service-t/src/com/android/server/net/NetworkStatsFactory.java
index c5a69c0..7d48b71 100644
--- a/service-t/src/com/android/server/net/NetworkStatsFactory.java
+++ b/service-t/src/com/android/server/net/NetworkStatsFactory.java
@@ -252,8 +252,6 @@
         synchronized (mPersistentDataLock) {
             // Take a reference. If this gets swapped out, we still have the old reference.
             final UnderlyingNetworkInfo[] vpnArray = mUnderlyingNetworkInfos;
-            // Take a defensive copy. mPersistSnapshot is mutated in some cases below
-            final NetworkStats prev = mPersistSnapshot.clone();
 
             requestSwapActiveStatsMapLocked();
             // Stats are always read from the inactive map, so they must be read after the
@@ -266,11 +264,27 @@
             mPersistSnapshot.setElapsedRealtime(diff.getElapsedRealtime());
             mPersistSnapshot.combineAllValues(filteredDiff);
 
-            NetworkStats adjustedStats = adjustForTunAnd464Xlat(mPersistSnapshot, prev, vpnArray);
+            // Update the stats adjusted for TunAnd464Xlat
+            // Apply 464xlat adjustments before VPN adjustments. If VPNs are using v4 on a v6 only
+            // network, the overhead is their fault.
+            // No locking here: apply464xlatAdjustments behaves fine with an add-only
+            // ConcurrentHashMap.
+            filteredDiff.apply464xlatAdjustments(mStackedIfaces);
+
+            // Migrate data usage over a VPN to the TUN network.
+            for (UnderlyingNetworkInfo info : vpnArray) {
+                filteredDiff.migrateTun(info.getOwnerUid(), info.getInterface(),
+                        info.getUnderlyingInterfaces());
+                // Filter out debug entries as that may lead to over counting.
+                filteredDiff.filterDebugEntries();
+            }
+
+            // Update mTunAnd464xlatAdjustedStats with migrated stats.
+            mTunAnd464xlatAdjustedStats.combineAllValues(filteredDiff);
+            mTunAnd464xlatAdjustedStats.setElapsedRealtime(filteredDiff.getElapsedRealtime());
 
             // Filter return values
-            adjustedStats.filter(limitUid, limitIfaces, limitTag);
-            return adjustedStats;
+            return mTunAnd464xlatAdjustedStats.filteredClone(limitUid, limitIfaces, limitTag);
         }
     }
 
@@ -309,33 +323,6 @@
         return filteredStats;
     }
 
-    @GuardedBy("mPersistentDataLock")
-    private NetworkStats adjustForTunAnd464Xlat(NetworkStats uidDetailStats,
-            NetworkStats previousStats, UnderlyingNetworkInfo[] vpnArray) {
-        // Calculate delta from last snapshot
-        final NetworkStats delta = uidDetailStats.subtract(previousStats);
-
-        // Apply 464xlat adjustments before VPN adjustments. If VPNs are using v4 on a v6 only
-        // network, the overhead is their fault.
-        // No locking here: apply464xlatAdjustments behaves fine with an add-only
-        // ConcurrentHashMap.
-        delta.apply464xlatAdjustments(mStackedIfaces);
-
-        // Migrate data usage over a VPN to the TUN network.
-        for (UnderlyingNetworkInfo info : vpnArray) {
-            delta.migrateTun(info.getOwnerUid(), info.getInterface(),
-                    info.getUnderlyingInterfaces());
-            // Filter out debug entries as that may lead to over counting.
-            delta.filterDebugEntries();
-        }
-
-        // Update mTunAnd464xlatAdjustedStats with migrated delta.
-        mTunAnd464xlatAdjustedStats.combineAllValues(delta);
-        mTunAnd464xlatAdjustedStats.setElapsedRealtime(uidDetailStats.getElapsedRealtime());
-
-        return mTunAnd464xlatAdjustedStats.clone();
-    }
-
     /**
      * Remove stats from {@code mPersistSnapshot} and {@code mTunAnd464xlatAdjustedStats} for the
      * given uids.