Merge "Free memory when it is no more used"
diff --git a/libnetdbpf/BpfNetworkStats.cpp b/libnetdbpf/BpfNetworkStats.cpp
index 013e7ef..c8bfbeb 100644
--- a/libnetdbpf/BpfNetworkStats.cpp
+++ b/libnetdbpf/BpfNetworkStats.cpp
@@ -162,6 +162,17 @@
               strerror(res.code()));
         return -res.code();
     }
+
+    // Since eBPF use hash map to record stats, network stats collected from
+    // eBPF will be out of order. And the performance of findIndexHinted in
+    // NetworkStats will also be impacted.
+    //
+    // Furthermore, since the StatsKey contains iface index, the network stats
+    // reported to framework would create items with the same iface, uid, tag
+    // and set, which causes NetworkStats maps wrong item to subtract.
+    //
+    // Thus, the stats needs to be properly sorted and grouped before reported.
+    groupNetworkStats(lines);
     return 0;
 }
 
@@ -227,6 +238,8 @@
               strerror(res.code()));
         return -res.code();
     }
+
+    groupNetworkStats(lines);
     return 0;
 }
 
@@ -253,5 +266,66 @@
     return (uint64_t)uid << 32 | tag;
 }
 
+void groupNetworkStats(std::vector<stats_line>* lines) {
+    if (lines->size() <= 1) return;
+    std::sort(lines->begin(), lines->end());
+
+    // Similar to std::unique(), but aggregates the duplicates rather than discarding them.
+    size_t nextOutput = 0;
+    for (size_t i = 1; i < lines->size(); i++) {
+        if (lines->at(nextOutput) == lines->at(i)) {
+            lines->at(nextOutput) += lines->at(i);
+        } else {
+            nextOutput++;
+            if (nextOutput != i) {
+                lines->at(nextOutput) = lines->at(i);
+            }
+        }
+    }
+
+    if (lines->size() != nextOutput + 1) {
+        lines->resize(nextOutput + 1);
+    }
+}
+
+// True if lhs equals to rhs, only compare iface, uid, tag and set.
+bool operator==(const stats_line& lhs, const stats_line& rhs) {
+    return ((lhs.uid == rhs.uid) && (lhs.tag == rhs.tag) && (lhs.set == rhs.set) &&
+            !strncmp(lhs.iface, rhs.iface, sizeof(lhs.iface)));
+}
+
+// True if lhs is smaller then rhs, only compare iface, uid, tag and set.
+bool operator<(const stats_line& lhs, const stats_line& rhs) {
+    int ret = strncmp(lhs.iface, rhs.iface, sizeof(lhs.iface));
+    if (ret != 0) return ret < 0;
+    ret = lhs.uid - rhs.uid;
+    if (ret != 0) return ret < 0;
+    ret = lhs.tag - rhs.tag;
+    if (ret != 0) return ret < 0;
+    ret = lhs.set - rhs.set;
+    if (ret != 0) return ret < 0;
+    return false;
+}
+
+stats_line& stats_line::operator=(const stats_line& rhs) {
+    strlcpy(iface, rhs.iface, sizeof(iface));
+    uid = rhs.uid;
+    set = rhs.set;
+    tag = rhs.tag;
+    rxPackets = rhs.rxPackets;
+    txPackets = rhs.txPackets;
+    rxBytes = rhs.rxBytes;
+    txBytes = rhs.txBytes;
+    return *this;
+}
+
+stats_line& stats_line::operator+=(const stats_line& rhs) {
+    rxPackets += rhs.rxPackets;
+    txPackets += rhs.txPackets;
+    rxBytes += rhs.rxBytes;
+    txBytes += rhs.txBytes;
+    return *this;
+}
+
 }  // namespace bpf
 }  // namespace android
diff --git a/libnetdbpf/BpfNetworkStatsTest.cpp b/libnetdbpf/BpfNetworkStatsTest.cpp
index 64dc7d4..432d9fc 100644
--- a/libnetdbpf/BpfNetworkStatsTest.cpp
+++ b/libnetdbpf/BpfNetworkStatsTest.cpp
@@ -431,14 +431,108 @@
     ASSERT_EQ(0,
               parseBpfNetworkStatsDevInternal(&lines, mFakeIfaceStatsMap, mFakeIfaceIndexNameMap));
     ASSERT_EQ((unsigned long)4, lines.size());
-    std::sort(lines.begin(), lines.end(), [](const auto& line1, const auto& line2)-> bool {
-        return strcmp(line1.iface, line2.iface) < 0;
-    });
+
     expectStatsLineEqual(value1, IFACE_NAME1, UID_ALL, SET_ALL, TAG_NONE, lines[0]);
     expectStatsLineEqual(value1, IFACE_NAME3, UID_ALL, SET_ALL, TAG_NONE, lines[1]);
     expectStatsLineEqual(value2, IFACE_NAME2, UID_ALL, SET_ALL, TAG_NONE, lines[2]);
     ASSERT_EQ(0, strcmp(TRUNCATED_IFACE_NAME, lines[3].iface));
     expectStatsLineEqual(value2, TRUNCATED_IFACE_NAME, UID_ALL, SET_ALL, TAG_NONE, lines[3]);
 }
+
+TEST_F(BpfNetworkStatsHelperTest, TestGetStatsSortedAndGrouped) {
+    SKIP_IF_BPF_NOT_SUPPORTED;
+
+    // Create iface indexes with duplicate iface name.
+    updateIfaceMap(IFACE_NAME1, IFACE_INDEX1);
+    updateIfaceMap(IFACE_NAME2, IFACE_INDEX2);
+    updateIfaceMap(IFACE_NAME1, IFACE_INDEX3);  // Duplicate!
+
+    StatsValue value1 = {
+            .rxBytes = TEST_BYTES0,
+            .rxPackets = TEST_PACKET0,
+            .txBytes = TEST_BYTES1,
+            .txPackets = TEST_PACKET1,
+    };
+    StatsValue value2 = {
+            .rxBytes = TEST_BYTES1,
+            .rxPackets = TEST_PACKET1,
+            .txBytes = TEST_BYTES0,
+            .txPackets = TEST_PACKET0,
+    };
+    StatsValue value3 = {
+            .rxBytes = TEST_BYTES0 * 2,
+            .rxPackets = TEST_PACKET0 * 2,
+            .txBytes = TEST_BYTES1 * 2,
+            .txPackets = TEST_PACKET1 * 2,
+    };
+
+    std::vector<stats_line> lines;
+    std::vector<std::string> ifaces;
+
+    // Test empty stats.
+    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL,
+                                                    mFakeTagStatsMap, mFakeIfaceIndexNameMap));
+    ASSERT_EQ((size_t) 0, lines.size());
+    lines.clear();
+
+    // Test 1 line stats.
+    populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1,
+                      mFakeTagStatsMap);
+    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL,
+                                                    mFakeTagStatsMap, mFakeIfaceIndexNameMap));
+    ASSERT_EQ((size_t) 1, lines.size());
+    expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, TEST_TAG, lines[0]);
+    lines.clear();
+
+    // These items should not be grouped.
+    populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX2, TEST_COUNTERSET0, value2,
+                      mFakeTagStatsMap);
+    populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX3, TEST_COUNTERSET1, value2,
+                      mFakeTagStatsMap);
+    populateFakeStats(TEST_UID1, TEST_TAG + 1, IFACE_INDEX1, TEST_COUNTERSET0, value2,
+                      mFakeTagStatsMap);
+    populateFakeStats(TEST_UID2, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1,
+                      mFakeTagStatsMap);
+    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL,
+                                                    mFakeTagStatsMap, mFakeIfaceIndexNameMap));
+    ASSERT_EQ((size_t) 5, lines.size());
+    lines.clear();
+
+    // These items should be grouped.
+    populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX3, TEST_COUNTERSET0, value1,
+                      mFakeTagStatsMap);
+    populateFakeStats(TEST_UID2, TEST_TAG, IFACE_INDEX3, TEST_COUNTERSET0, value1,
+                      mFakeTagStatsMap);
+
+    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL,
+                                                    mFakeTagStatsMap, mFakeIfaceIndexNameMap));
+    ASSERT_EQ((size_t) 5, lines.size());
+
+    // Verify Sorted & Grouped.
+    expectStatsLineEqual(value3, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, TEST_TAG, lines[0]);
+    expectStatsLineEqual(value2, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET1, TEST_TAG, lines[1]);
+    expectStatsLineEqual(value2, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, TEST_TAG + 1, lines[2]);
+    expectStatsLineEqual(value3, IFACE_NAME1, TEST_UID2, TEST_COUNTERSET0, TEST_TAG, lines[3]);
+    expectStatsLineEqual(value2, IFACE_NAME2, TEST_UID1, TEST_COUNTERSET0, TEST_TAG, lines[4]);
+    lines.clear();
+
+    // Perform test on IfaceStats.
+    uint32_t ifaceStatsKey = IFACE_INDEX2;
+    EXPECT_TRUE(isOk(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value2, BPF_ANY)));
+    ifaceStatsKey = IFACE_INDEX1;
+    EXPECT_TRUE(isOk(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value1, BPF_ANY)));
+
+    // This should be grouped.
+    ifaceStatsKey = IFACE_INDEX3;
+    EXPECT_TRUE(isOk(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value1, BPF_ANY)));
+
+    ASSERT_EQ(0,
+              parseBpfNetworkStatsDevInternal(&lines, mFakeIfaceStatsMap, mFakeIfaceIndexNameMap));
+    ASSERT_EQ((size_t) 2, lines.size());
+
+    expectStatsLineEqual(value3, IFACE_NAME1, UID_ALL, SET_ALL, TAG_NONE, lines[0]);
+    expectStatsLineEqual(value2, IFACE_NAME2, UID_ALL, SET_ALL, TAG_NONE, lines[1]);
+    lines.clear();
+}
 }  // namespace bpf
 }  // namespace android
diff --git a/libnetdbpf/include/netdbpf/BpfNetworkStats.h b/libnetdbpf/include/netdbpf/BpfNetworkStats.h
index 80dff88..2ceae98 100644
--- a/libnetdbpf/include/netdbpf/BpfNetworkStats.h
+++ b/libnetdbpf/include/netdbpf/BpfNetworkStats.h
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+#ifndef _BPF_NETWORKSTATS_H
+#define _BPF_NETWORKSTATS_H
+
 #include <bpf/BpfMap.h>
 
 namespace android {
@@ -43,7 +46,14 @@
     int64_t rxPackets;
     int64_t txBytes;
     int64_t txPackets;
+
+    stats_line& operator=(const stats_line& rhs);
+    stats_line& operator+=(const stats_line& rhs);
 };
+
+bool operator==(const stats_line& lhs, const stats_line& rhs);
+bool operator<(const stats_line& lhs, const stats_line& rhs);
+
 // For test only
 int bpfGetUidStatsInternal(uid_t uid, struct Stats* stats,
                            const BpfMap<uint32_t, StatsValue>& appUidStatsMap);
@@ -107,6 +117,9 @@
                                int limitUid);
 
 int parseBpfNetworkStatsDev(std::vector<stats_line>* lines);
+void groupNetworkStats(std::vector<stats_line>* lines);
 int cleanStatsMap();
 }  // namespace bpf
 }  // namespace android
+
+#endif  // _BPF_NETWORKSTATS_H