register callback to pull NetworkBpfMapInfo atom

Test: statsd_testdrive 10161, atest BpfNetMaps
Bug: 217624062
Bug: 245228691

Change-Id: I630a86cb5a34aaa1810eb75b374588dba570f0ff
diff --git a/service/Android.bp b/service/Android.bp
index b68d389..68c1722 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -143,6 +143,7 @@
         "src/**/*.java",
         ":framework-connectivity-shared-srcs",
         ":services-connectivity-shared-srcs",
+        ":statslog-connectivity-java-gen",
     ],
     libs: [
         "framework-annotations-lib",
@@ -152,6 +153,7 @@
         "framework-wifi.stubs.module_lib",
         "unsupportedappusage",
         "ServiceConnectivityResources",
+        "framework-statsd.stubs.module_lib",
     ],
     static_libs: [
         // Do not add libs here if they are already included
@@ -332,3 +334,10 @@
         "--output $(out)",
     visibility: ["//visibility:private"],
 }
+
+genrule {
+  name: "statslog-connectivity-java-gen",
+  tools: ["stats-log-api-gen"],
+  cmd: "$(location stats-log-api-gen) --java $(out) --module connectivity --javaPackage com.android.server --javaClass ConnectivityStatsLog",
+  out: ["com/android/server/ConnectivityStatsLog.java"],
+}
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index 4eeef5c..69b6486 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -33,6 +33,9 @@
 import static android.system.OsConstants.ENOENT;
 import static android.system.OsConstants.EOPNOTSUPP;
 
+import static com.android.server.ConnectivityStatsLog.NETWORK_BPF_MAP_INFO;
+
+import android.app.StatsManager;
 import android.content.Context;
 import android.net.INetd;
 import android.os.RemoteException;
@@ -42,12 +45,15 @@
 import android.system.Os;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.StatsEvent;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.BackgroundThread;
 import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.BpfMap;
 import com.android.net.module.util.DeviceConfigUtils;
 import com.android.net.module.util.IBpfMap;
+import com.android.net.module.util.Struct;
 import com.android.net.module.util.Struct.U32;
 import com.android.net.module.util.Struct.U8;
 import com.android.net.module.util.bpf.CookieTagMapKey;
@@ -55,6 +61,7 @@
 
 import java.io.FileDescriptor;
 import java.io.IOException;
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -274,6 +281,15 @@
         public int synchronizeKernelRCU() {
             return native_synchronizeKernelRCU();
         }
+
+        /**
+         * Build Stats Event for NETWORK_BPF_MAP_INFO atom
+         */
+        public StatsEvent buildStatsEvent(final int cookieTagMapSize, final int uidOwnerMapSize,
+                final int uidPermissionMapSize) {
+            return ConnectivityStatsLog.buildStatsEvent(NETWORK_BPF_MAP_INFO, cookieTagMapSize,
+                    uidOwnerMapSize, uidPermissionMapSize);
+        }
     }
 
     /** Constructor used after T that doesn't need to use netd anymore. */
@@ -849,6 +865,43 @@
         }
     }
 
+    /** Register callback for statsd to pull atom. */
+    public void setPullAtomCallback(final Context context) {
+        throwIfPreT("setPullAtomCallback is not available on pre-T devices");
+
+        final StatsManager statsManager = context.getSystemService(StatsManager.class);
+        statsManager.setPullAtomCallback(NETWORK_BPF_MAP_INFO, null /* metadata */,
+                BackgroundThread.getExecutor(), this::pullBpfMapInfoAtom);
+    }
+
+    private <K extends Struct, V extends Struct> int getMapSize(IBpfMap<K, V> map)
+            throws ErrnoException {
+        // forEach could restart iteration from the beginning if there is a concurrent entry
+        // deletion. netd and skDestroyListener could delete CookieTagMap entry concurrently.
+        // So using Set to count the number of entry in the map.
+        Set<K> keySet = new ArraySet<>();
+        map.forEach((k, v) -> keySet.add(k));
+        return keySet.size();
+    }
+
+    /** Callback for StatsManager#setPullAtomCallback */
+    @VisibleForTesting
+    public int pullBpfMapInfoAtom(final int atomTag, final List<StatsEvent> data) {
+        if (atomTag != NETWORK_BPF_MAP_INFO) {
+            Log.e(TAG, "Unexpected atom tag: " + atomTag);
+            return StatsManager.PULL_SKIP;
+        }
+
+        try {
+            data.add(mDeps.buildStatsEvent(getMapSize(sCookieTagMap), getMapSize(sUidOwnerMap),
+                    getMapSize(sUidPermissionMap)));
+        } catch (ErrnoException e) {
+            Log.e(TAG, "Failed to pull NETWORK_BPF_MAP_INFO atom: " + e);
+            return StatsManager.PULL_SKIP;
+        }
+        return StatsManager.PULL_SUCCESS;
+    }
+
     /**
      * Dump BPF maps
      *
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index afb3ca2..36c81dd 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -3040,6 +3040,11 @@
         if (!ConnectivitySettingsManager.getMobileDataPreferredUids(mContext).isEmpty()) {
             updateMobileDataPreferredUids();
         }
+
+        // On T+ devices, register callback for statsd to pull NETWORK_BPF_MAP_INFO atom
+        if (SdkLevel.isAtLeastT()) {
+            mBpfNetMaps.setPullAtomCallback(mContext);
+        }
     }
 
     /**