Move Interface stats map dump to NetworkStatsService

Bug: 217624062
Test: dumpsys netstats, atest NetworkStatsServiceTest
Change-Id: Ie0357a79925c0bbb34aa05442f727c776f434f88
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index 0da7b6f..0e42959 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -254,6 +254,8 @@
             "/sys/fs/bpf/netd_shared/map_netd_stats_map_A";
     private static final String STATS_MAP_B_PATH =
             "/sys/fs/bpf/netd_shared/map_netd_stats_map_B";
+    private static final String IFACE_STATS_MAP_PATH =
+            "/sys/fs/bpf/netd_shared/map_netd_iface_stats_map";
 
     /**
      * DeviceConfig flag used to indicate whether the files should be stored in the apex data
@@ -413,6 +415,7 @@
     private final IBpfMap<StatsMapKey, StatsMapValue> mStatsMapA;
     private final IBpfMap<StatsMapKey, StatsMapValue> mStatsMapB;
     private final IBpfMap<UidStatsMapKey, StatsMapValue> mAppUidStatsMap;
+    private final IBpfMap<S32, StatsMapValue> mIfaceStatsMap;
 
     /** Data layer operation counters for splicing into other structures. */
     private NetworkStats mUidOperations = new NetworkStats(0L, 10);
@@ -592,6 +595,7 @@
         mStatsMapA = mDeps.getStatsMapA();
         mStatsMapB = mDeps.getStatsMapB();
         mAppUidStatsMap = mDeps.getAppUidStatsMap();
+        mIfaceStatsMap = mDeps.getIfaceStatsMap();
 
         // TODO: Remove bpfNetMaps creation and always start SkDestroyListener
         // Following code is for the experiment to verify the SkDestroyListener refactoring. Based
@@ -795,6 +799,16 @@
             }
         }
 
+        /** Gets interface stats map */
+        public IBpfMap<S32, StatsMapValue> getIfaceStatsMap() {
+            try {
+                return new BpfMap<S32, StatsMapValue>(IFACE_STATS_MAP_PATH,
+                        BpfMap.BPF_F_RDWR, S32.class, StatsMapValue.class);
+            } catch (ErrnoException e) {
+                throw new IllegalStateException("Failed to open interface stats map", e);
+            }
+        }
+
         /** Gets whether the build is userdebug. */
         public boolean isDebuggable() {
             return Build.isDebuggable();
@@ -2763,6 +2777,7 @@
             dumpAppUidStatsMapLocked(pw);
             dumpStatsMapLocked(mStatsMapA, pw, "mStatsMapA");
             dumpStatsMapLocked(mStatsMapB, pw, "mStatsMapB");
+            dumpIfaceStatsMapLocked(pw);
             pw.decreaseIndent();
         }
     }
@@ -2850,6 +2865,8 @@
         pw.println("mAppUidStatsMap: " + getMapStatus(mAppUidStatsMap, APP_UID_STATS_MAP_PATH));
         pw.println("mStatsMapA: " + getMapStatus(mStatsMapA, STATS_MAP_A_PATH));
         pw.println("mStatsMapB: " + getMapStatus(mStatsMapB, STATS_MAP_B_PATH));
+        // mIfaceStatsMap is always not null but dump status to be consistent with other maps.
+        pw.println("mIfaceStatsMap: " + getMapStatus(mIfaceStatsMap, IFACE_STATS_MAP_PATH));
     }
 
     @GuardedBy("mStatsLock")
@@ -2909,6 +2926,21 @@
                 });
     }
 
+    @GuardedBy("mStatsLock")
+    private void dumpIfaceStatsMapLocked(final IndentingPrintWriter pw) {
+        BpfDump.dumpMap(mIfaceStatsMap, pw, "mIfaceStatsMap",
+                "ifaceIndex ifaceName rxBytes rxPackets txBytes txPackets",
+                (key, value) -> {
+                    final String ifName = mInterfaceMapUpdater.getIfNameByIndex(key.val);
+                    return key.val + " "
+                            + (ifName != null ? ifName : "unknown") + " "
+                            + value.rxBytes + " "
+                            + value.rxPackets + " "
+                            + value.txBytes + " "
+                            + value.txPackets;
+                });
+    }
+
     private NetworkStats readNetworkStatsSummaryDev() {
         try {
             return mStatsFactory.readNetworkStatsSummaryDev();
diff --git a/service/native/TrafficController.cpp b/service/native/TrafficController.cpp
index 245b9d8..fc76ae5 100644
--- a/service/native/TrafficController.cpp
+++ b/service/native/TrafficController.cpp
@@ -647,25 +647,6 @@
         dw.println("mCookieTagMap print end with error: %s", res.error().message().c_str());
     }
 
-    // Print ifaceStatsMap content
-    std::string ifaceStatsHeader = StringPrintf("ifaceIndex ifaceName rxBytes rxPackets txBytes"
-                                                " txPackets");
-    dumpBpfMap("mIfaceStatsMap:", dw, ifaceStatsHeader);
-    const auto printIfaceStatsInfo = [&dw, this](const uint32_t& key, const StatsValue& value,
-                                                 const BpfMap<uint32_t, StatsValue>&) {
-        auto ifname = mIfaceIndexNameMap.readValue(key);
-        if (!ifname.ok()) {
-            ifname = IfaceValue{"unknown"};
-        }
-        dw.println("%u %s %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64, key, ifname.value().name,
-                   value.rxBytes, value.rxPackets, value.txBytes, value.txPackets);
-        return base::Result<void>();
-    };
-    res = mIfaceStatsMap.iterateWithValue(printIfaceStatsInfo);
-    if (!res.ok()) {
-        dw.println("mIfaceStatsMap print end with error: %s", res.error().message().c_str());
-    }
-
     dw.blankline();
 }
 
diff --git a/service/native/TrafficControllerTest.cpp b/service/native/TrafficControllerTest.cpp
index 2f5960f..6cb0940 100644
--- a/service/native/TrafficControllerTest.cpp
+++ b/service/native/TrafficControllerTest.cpp
@@ -805,8 +805,7 @@
             "Read value of map -1 failed: Bad file descriptor";
 
     std::vector<std::string> expectedLines = {
-        fmt::format("mCookieTagMap {}", kErrIterate),
-        fmt::format("mIfaceStatsMap {}", kErrIterate)};
+        fmt::format("mCookieTagMap {}", kErrIterate)};
     EXPECT_TRUE(expectDumpsysContains(expectedLines));
 }
 
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index 6448819..2ceb00a 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -263,7 +263,8 @@
             StatsMapValue.class);
     private TestBpfMap<UidStatsMapKey, StatsMapValue> mAppUidStatsMap = new TestBpfMap<>(
             UidStatsMapKey.class, StatsMapValue.class);
-
+    private TestBpfMap<S32, StatsMapValue> mIfaceStatsMap = new TestBpfMap<>(
+            S32.class, StatsMapValue.class);
     private NetworkStatsService mService;
     private INetworkStatsSession mSession;
     private AlertObserver mAlertObserver;
@@ -503,6 +504,11 @@
             }
 
             @Override
+            public IBpfMap<S32, StatsMapValue> getIfaceStatsMap() {
+                return mIfaceStatsMap;
+            }
+
+            @Override
             public boolean isDebuggable() {
                 return mIsDebuggable == Boolean.TRUE;
             }
@@ -2552,4 +2558,25 @@
         doReturn(null).when(mBpfInterfaceMapUpdater).getIfNameByIndex(10 /* index */);
         doTestDumpStatsMap("unknown");
     }
+
+    void doTestDumpIfaceStatsMap(final String expectedIfaceName) throws Exception {
+        mIfaceStatsMap.insertEntry(new S32(10), new StatsMapValue(3, 3000, 3, 3000));
+
+        final String dump = getDump();
+        assertDumpContains(dump, "mIfaceStatsMap: OK");
+        assertDumpContains(dump, "ifaceIndex ifaceName rxBytes rxPackets txBytes txPackets");
+        assertDumpContains(dump, "10 " + expectedIfaceName + " 3000 3 3000 3");
+    }
+
+    @Test
+    public void testDumpIfaceStatsMap() throws Exception {
+        doReturn("wlan0").when(mBpfInterfaceMapUpdater).getIfNameByIndex(10 /* index */);
+        doTestDumpIfaceStatsMap("wlan0");
+    }
+
+    @Test
+    public void testDumpIfaceStatsMapUnknownInterface() throws Exception {
+        doReturn(null).when(mBpfInterfaceMapUpdater).getIfNameByIndex(10 /* index */);
+        doTestDumpIfaceStatsMap("unknown");
+    }
 }