TcpSocketMonitor: Add stats tables per sockets and per networks

This patch defines SocketEntry and TcpStats for tracking inside
TcpSocketMonitor per-socket and per-network tcp statistics.

For the time being, the following data is tracked:
 - total packet sent and lost
 - average rtt
 - time diff between last packet sent and last ack received

Bug: 64147860
Test: tested manually, watching the result of
      $ adb shell dumpsys netd tcp_socket_info
Change-Id: If032a4884ac5dcd693ccfc4bc738167d848a9833
diff --git a/server/TcpSocketMonitor.cpp b/server/TcpSocketMonitor.cpp
index 4a0a217..a6d71b7 100644
--- a/server/TcpSocketMonitor.cpp
+++ b/server/TcpSocketMonitor.cpp
@@ -18,13 +18,13 @@
 
 #include <iomanip>
 #include <thread>
+#include <vector>
 
 #include <arpa/inet.h>
 #include <netinet/tcp.h>
 #include <linux/tcp.h>
 
 #include "DumpWriter.h"
-#include "Fwmark.h"
 #include "SockDiag.h"
 #include "TcpSocketMonitor.h"
 
@@ -32,7 +32,6 @@
 namespace net {
 
 using std::chrono::duration_cast;
-using std::chrono::milliseconds;
 using std::chrono::steady_clock;
 
 constexpr const char* getTcpStateName(int t) {
@@ -78,7 +77,7 @@
 
     dw.println(
             "netId=%d uid=%u mark=0x%x saddr=%s daddr=%s sport=%u dport=%u tcp_state=%s(%u) "
-            "rqueue=%u wqueue=%u rtt=%gms var_rtt=%gms rcv_rtt=%gms unacked=%u snd_cwnd=%u",
+            "rtt=%gms sent=%u lost=%u",
             mark.netId,
             sockinfo->idiag_uid,
             mark.intValue,
@@ -87,13 +86,9 @@
             ntohs(sockinfo->id.idiag_sport),
             ntohs(sockinfo->id.idiag_dport),
             getTcpStateName(sockinfo->idiag_state), sockinfo->idiag_state,
-            sockinfo->idiag_rqueue,
-            sockinfo->idiag_wqueue,
             tcpinfo_get(tcpinfo, tcpi_rtt, tcpinfoLen, 0) / 1000.0,
-            tcpinfo_get(tcpinfo, tcpi_rttvar, tcpinfoLen, 0) / 1000.0,
-            tcpinfo_get(tcpinfo, tcpi_rcv_rtt, tcpinfoLen, 0) / 1000.0,
-            tcpinfo_get(tcpinfo, tcpi_unacked, tcpinfoLen, 0),
-            tcpinfo_get(tcpinfo, tcpi_data_segs_out, tcpinfoLen, 0));
+            tcpinfo_get(tcpinfo, tcpi_data_segs_out, tcpinfoLen, 0),
+            tcpinfo_get(tcpinfo, tcpi_lost, tcpinfoLen, 0));
 }
 
 const String16 TcpSocketMonitor::DUMP_KEYWORD = String16("tcp_socket_info");
@@ -107,22 +102,48 @@
 
     const auto now = steady_clock::now();
     const auto d = duration_cast<milliseconds>(now - mLastPoll);
-    dw.println("last poll %lld ms ago", d.count());
+    dw.println("running=%d, suspended=%d, last poll %lld ms ago",
+            mIsRunning, mIsSuspended, d.count());
 
-    SockDiag sd;
-    if (!sd.open()) {
-        ALOGE("Error opening sock diag for dumping TCP socket info");
-        return;
+    if (!mNetworkStats.empty()) {
+        dw.blankline();
+        dw.println("Network stats:");
+        for (auto const& stats : mNetworkStats) {
+            if (stats.second.nSockets == 0) {
+                continue;
+            }
+            dw.println("netId=%d sent=%d lost=%d rttMs=%gms sentAckDiff=%gms",
+                    stats.first,
+                    stats.second.sent,
+                    stats.second.lost,
+                    stats.second.rttUs / 1000.0 / stats.second.nSockets,
+                    stats.second.sentAckDiffMs / stats.second.nSockets);
+        }
     }
 
-    const auto tcpInfoReader = [&dw](Fwmark mark, const struct inet_diag_msg *sockinfo,
-                                     const struct tcp_info *tcpinfo, uint32_t tcpinfoLen) {
-        tcpInfoPrint(dw, mark, sockinfo, tcpinfo, tcpinfoLen);
-    };
+    if (!mSocketEntries.empty()) {
+        dw.blankline();
+        dw.println("Socket entries:");
+        for (auto const& stats : mSocketEntries) {
+            dw.println("netId=%u uid=%u cookie=%ld",
+                    stats.second.mark.netId, stats.second.uid, stats.first);
+        }
+    }
 
-    if (int ret = sd.getLiveTcpInfos(tcpInfoReader)) {
-        ALOGE("Failed to dump TCP socket info: %s", strerror(-ret));
-        return;
+    SockDiag sd;
+    if (sd.open()) {
+        dw.blankline();
+        dw.println("Current socket dump:");
+        const auto tcpInfoReader = [&dw](Fwmark mark, const struct inet_diag_msg *sockinfo,
+                                         const struct tcp_info *tcpinfo, uint32_t tcpinfoLen) {
+            tcpInfoPrint(dw, mark, sockinfo, tcpinfo, tcpinfoLen);
+        };
+
+        if (int ret = sd.getLiveTcpInfos(tcpInfoReader)) {
+            ALOGE("Failed to dump TCP socket info: %s", strerror(-ret));
+        }
+    } else {
+        ALOGE("Error opening sock diag for dumping TCP socket info");
     }
 
     dw.decIndent();
@@ -137,28 +158,29 @@
 }
 
 void TcpSocketMonitor::resumePolling() {
+    bool wasSuspended;
     {
         std::lock_guard<std::mutex> guard(mLock);
 
-        if (!mIsSuspended) {
-            return;
-        }
-
+        wasSuspended = mIsSuspended;
         mIsSuspended = false;
-
-        ALOGD("resuming tcpinfo polling with polling interval set to %lld ms",
-                mNextSleepDurationMs.count());
+        ALOGD("resuming tcpinfo polling (interval=%lldms)", mNextSleepDurationMs.count());
     }
 
-    mCv.notify_all();
+    if (wasSuspended) {
+        mCv.notify_all();
+    }
 }
 
 void TcpSocketMonitor::suspendPolling() {
     std::lock_guard<std::mutex> guard(mLock);
 
-    if (!mIsSuspended) {
-        ALOGD("suspending tcpinfo polling");
-        mIsSuspended = true;
+    bool wasSuspended = mIsSuspended;
+    mIsSuspended = true;
+    ALOGD("suspending tcpinfo polling");
+
+    if (!wasSuspended) {
+        mSocketEntries.clear();
     }
 }
 
@@ -169,28 +191,38 @@
         return;
     }
 
-    const auto now = steady_clock::now();
-
     SockDiag sd;
     if (!sd.open()) {
         ALOGE("Error opening sock diag for polling TCP socket info");
         return;
     }
 
-    const auto tcpInfoReader = [](Fwmark mark, const struct inet_diag_msg *sockinfo,
-                                const struct tcp_info *tcpinfo, uint32_t tcpinfoLen) {
+    const auto now = steady_clock::now();
+    const auto tcpInfoReader = [this, now](Fwmark mark, const struct inet_diag_msg *sockinfo,
+                                           const struct tcp_info *tcpinfo,
+                                           uint32_t tcpinfoLen) NO_THREAD_SAFETY_ANALYSIS {
         if (sockinfo == nullptr || tcpinfo == nullptr || tcpinfoLen == 0 || mark.intValue == 0) {
             return;
         }
-
-        // TODO: process socket stats
+        updateSocketStats(now, mark, sockinfo, tcpinfo, tcpinfoLen);
     };
 
+    // Reset mNetworkStats
+    mNetworkStats.clear();
+
     if (int ret = sd.getLiveTcpInfos(tcpInfoReader)) {
         ALOGE("Failed to poll TCP socket info: %s", strerror(-ret));
         return;
     }
 
+    // Remove any SocketEntry not updated
+    for (auto it = mSocketEntries.cbegin(); it != mSocketEntries.cend();) {
+        if (it->second.lastUpdate < now) {
+            it = mSocketEntries.erase(it);
+        } else {
+            it++;
+        }
+    }
     mLastPoll = now;
 }
 
@@ -216,12 +248,54 @@
     return mIsRunning;
 }
 
+void TcpSocketMonitor::updateSocketStats(time_point now, Fwmark mark,
+                                         const struct inet_diag_msg *sockinfo,
+                                         const struct tcp_info *tcpinfo,
+                                         uint32_t tcpinfoLen) NO_THREAD_SAFETY_ANALYSIS {
+    int32_t lastAck = tcpinfo_get(tcpinfo, tcpi_last_ack_recv, tcpinfoLen, 0);
+    int32_t lastSent = tcpinfo_get(tcpinfo, tcpi_last_data_sent, tcpinfoLen, 0);
+    TcpStats diff = {
+        .sent = tcpinfo_get(tcpinfo, tcpi_data_segs_out, tcpinfoLen, 0),
+        .lost = tcpinfo_get(tcpinfo, tcpi_lost, tcpinfoLen, 0),
+        .rttUs = tcpinfo_get(tcpinfo, tcpi_rtt, tcpinfoLen, 0),
+        .sentAckDiffMs = lastAck - lastSent,
+        .nSockets = 1,
+    };
+
+    {
+        // Update socket stats with the newest entry, computing the diff w.r.t the previous entry.
+        const uint64_t cookie = (static_cast<uint64_t>(sockinfo->id.idiag_cookie[0]) << 32)
+                | static_cast<uint64_t>(sockinfo->id.idiag_cookie[1]);
+        const SocketEntry previous = mSocketEntries[cookie];
+        mSocketEntries[cookie] = {
+            .sent = diff.sent,
+            .lost = diff.lost,
+            .lastUpdate = now,
+            .mark = mark,
+            .uid = sockinfo->idiag_uid,
+        };
+
+        diff.sent -= previous.sent;
+        diff.lost -= previous.lost;
+    }
+
+    {
+        // Aggregate the diff per network id.
+        auto& stats = mNetworkStats[mark.netId];
+        stats.sent += diff.sent;
+        stats.lost += diff.lost;
+        stats.rttUs += diff.rttUs;
+        stats.sentAckDiffMs += diff.sentAckDiffMs;
+        stats.nSockets += diff.nSockets;
+    }
+}
+
 TcpSocketMonitor::TcpSocketMonitor() {
     std::lock_guard<std::mutex> guard(mLock);
 
     mNextSleepDurationMs = kDefaultPollingInterval;
-    mIsSuspended = true;
     mIsRunning = true;
+    mIsSuspended = true;
     mPollingThread = std::thread([this] {
         while (isRunning()) {
             poll();