dmabuf_dump: Change the default view to per-process stats

Add '-a' commandline option to show the old complete table that shows
dmabuf x process grid.

Fixes: 135668399
Test: dmabuf_dump
Test: dmabuf_dump -h
Test: dmabuf_dump -a
Test: dmabuf_dump -a 123
Test: dmabuf_dump 123 456
Test: dmabuf_dump <pid>

Change-Id: If228c97baa375c515d94baf3b5be674cfcc09919
Merged-In: If228c97baa375c515d94baf3b5be674cfcc09919
Signed-off-by: Sandeep Patil <sspatil@google.com>
diff --git a/libmeminfo/libdmabufinfo/include/dmabufinfo/dmabufinfo.h b/libmeminfo/libdmabufinfo/include/dmabufinfo/dmabufinfo.h
index a16c3fd..a6e7f69 100644
--- a/libmeminfo/libdmabufinfo/include/dmabufinfo/dmabufinfo.h
+++ b/libmeminfo/libdmabufinfo/include/dmabufinfo/dmabufinfo.h
@@ -19,6 +19,7 @@
 #include <sys/types.h>
 #include <unistd.h>
 
+#include <set>
 #include <string>
 #include <vector>
 #include <unordered_map>
@@ -33,6 +34,7 @@
         : inode_(inode), size_(size), count_(count), exporter_(exporter), name_(name) {
         total_refs_ = 0;
     }
+    DmaBuffer() = default;
     ~DmaBuffer() = default;
 
     // Adds one file descriptor reference for the given pid
@@ -54,11 +56,13 @@
     ino_t inode() const { return inode_; }
     uint64_t total_refs() const { return total_refs_; }
     uint64_t count() const { return count_; };
+    const std::set<pid_t>& pids() const { return pids_; }
     const std::string& name() const { return name_; }
     const std::string& exporter() const { return exporter_; }
     void SetName(const std::string& name) { name_ = name; }
     void SetExporter(const std::string& exporter) { exporter_ = exporter; }
     void SetCount(uint64_t count) { count_ = count; }
+    uint64_t Pss() const { return size_ / pids_.size(); }
 
     bool operator==(const DmaBuffer& rhs) {
         return (inode_ == rhs.inode()) && (size_ == rhs.size()) && (name_ == rhs.name()) &&
@@ -70,6 +74,7 @@
     uint64_t size_;
     uint64_t count_;
     uint64_t total_refs_;
+    std::set<pid_t> pids_;
     std::string exporter_;
     std::string name_;
     std::unordered_map<pid_t, int> fdrefs_;
@@ -80,6 +85,7 @@
         auto [it, inserted] = map->insert(std::make_pair(pid, 1));
         if (!inserted)
             it->second++;
+        pids_.insert(pid);
     }
 };
 
diff --git a/libmeminfo/libdmabufinfo/tools/dmabuf_dump.cpp b/libmeminfo/libdmabufinfo/tools/dmabuf_dump.cpp
index 9a80c82..48901b1 100644
--- a/libmeminfo/libdmabufinfo/tools/dmabuf_dump.cpp
+++ b/libmeminfo/libdmabufinfo/tools/dmabuf_dump.cpp
@@ -16,6 +16,7 @@
 
 #include <dirent.h>
 #include <errno.h>
+#include <getopt.h>
 #include <inttypes.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -36,9 +37,10 @@
 
 [[noreturn]] static void usage(int exit_status) {
     fprintf(stderr,
-            "Usage: %s [PID] \n"
-            "\t If PID is supplied, the dmabuf information for this process is shown.\n"
-            "\t Otherwise, shows the information for all processes.\n",
+            "Usage: %s [-ah] [PID] \n"
+            "-a\t show all dma buffers (ion) in big table, [buffer x process] grid \n"
+            "-h\t show this help\n"
+            "  \t If PID is supplied, the dmabuf information for that process is shown.\n",
             getprogname());
 
     exit(exit_status);
@@ -54,11 +56,7 @@
     return line;
 }
 
-static void AddPidsToSet(const std::unordered_map<pid_t, int>& map, std::set<pid_t>* set) {
-    for (auto it = map.begin(); it != map.end(); ++it) set->insert(it->first);
-}
-
-static void PrintDmaBufInfo(const std::vector<DmaBuffer>& bufs) {
+static void PrintDmaBufTable(const std::vector<DmaBuffer>& bufs) {
     if (bufs.empty()) {
         printf("dmabuf info not found ¯\\_(ツ)_/¯\n");
         return;
@@ -66,9 +64,8 @@
 
     // Find all unique pids in the input vector, create a set
     std::set<pid_t> pid_set;
-    for (int i = 0; i < bufs.size(); i++) {
-        AddPidsToSet(bufs[i].fdrefs(), &pid_set);
-        AddPidsToSet(bufs[i].maprefs(), &pid_set);
+    for (auto& buf : bufs) {
+        pid_set.insert(buf.pids().begin(), buf.pids().end());
     }
 
     // Format the header string spaced and separated with '|'
@@ -126,50 +123,144 @@
     return;
 }
 
-int main(int argc, char* argv[]) {
-    pid_t pid = -1;
-    std::vector<DmaBuffer> bufs;
-    bool show_all = true;
+static void PrintDmaBufPerProcess(const std::vector<DmaBuffer>& bufs) {
+    if (bufs.empty()) {
+        printf("dmabuf info not found ¯\\_(ツ)_/¯\n");
+        return;
+    }
 
-    if (argc > 1) {
-        if (sscanf(argv[1], "%d", &pid) == 1) {
-            show_all = false;
-        } else {
+    // Create a reverse map from pid to dmabufs
+    std::unordered_map<pid_t, std::set<ino_t>> pid_to_inodes = {};
+    uint64_t total_size = 0;  // Total size of dmabufs in the system
+    uint64_t kernel_rss = 0;  // Total size of dmabufs NOT mapped or opened by a process
+    for (auto& buf : bufs) {
+        for (auto pid : buf.pids()) {
+            pid_to_inodes[pid].insert(buf.inode());
+        }
+        total_size += buf.size();
+        if (buf.fdrefs().empty() && buf.maprefs().empty()) {
+            kernel_rss += buf.size();
+        }
+    }
+    // Create an inode to dmabuf map. We know inodes are unique..
+    std::unordered_map<ino_t, DmaBuffer> inode_to_dmabuf;
+    for (auto buf : bufs) {
+        inode_to_dmabuf[buf.inode()] = buf;
+    }
+
+    uint64_t total_rss = 0, total_pss = 0;
+    for (auto& [pid, inodes] : pid_to_inodes) {
+        uint64_t pss = 0;
+        uint64_t rss = 0;
+
+        printf("%16s:%-5d\n", GetProcessComm(pid).c_str(), pid);
+        printf("%22s %16s %16s %16s %16s\n", "Name", "Rss", "Pss", "nr_procs", "Inode");
+        for (auto& inode : inodes) {
+            DmaBuffer& buf = inode_to_dmabuf[inode];
+            printf("%22s %13" PRIu64 " kB %13" PRIu64 " kB %16zu %16" PRIuMAX "\n",
+                   buf.name().empty() ? "<unknown>" : buf.name().c_str(), buf.size() / 1024,
+                   buf.Pss() / 1024, buf.pids().size(), static_cast<uintmax_t>(buf.inode()));
+            rss += buf.size();
+            pss += buf.Pss();
+        }
+        printf("%22s %13" PRIu64 " kB %13" PRIu64 " kB %16s\n", "PROCESS TOTAL", rss / 1024,
+               pss / 1024, "");
+        printf("----------------------\n");
+        total_rss += rss;
+        total_pss += pss;
+    }
+    printf("dmabuf total: %" PRIu64 " kB kernel_rss: %" PRIu64 " kB userspace_rss: %" PRIu64
+           " kB userspace_pss: %" PRIu64 " kB\n ",
+           total_size / 1024, kernel_rss / 1024, total_rss / 1024, total_pss / 1024);
+}
+
+static bool ReadDmaBufs(std::vector<DmaBuffer>* bufs) {
+    bufs->clear();
+
+    if (!ReadDmaBufInfo(bufs)) {
+        fprintf(stderr, "debugfs entry for dmabuf not available, skipping\n");
+        return false;
+    }
+
+    std::unique_ptr<DIR, int (*)(DIR*)> dir(opendir("/proc"), closedir);
+    if (!dir) {
+        fprintf(stderr, "Failed to open /proc directory\n");
+        bufs->clear();
+        return false;
+    }
+
+    struct dirent* dent;
+    while ((dent = readdir(dir.get()))) {
+        if (dent->d_type != DT_DIR) continue;
+
+        int pid = atoi(dent->d_name);
+        if (pid == 0) {
+            continue;
+        }
+
+        if (!AppendDmaBufInfo(pid, bufs)) {
+            fprintf(stderr, "Unable to read dmabuf info for pid %d\n", pid);
+            bufs->clear();
+            return false;
+        }
+    }
+
+    return true;
+}
+
+int main(int argc, char* argv[]) {
+    struct option longopts[] = {{"all", no_argument, nullptr, 'a'},
+                                {"help", no_argument, nullptr, 'h'},
+                                {0, 0, nullptr, 0}};
+
+    int opt;
+    bool show_table = false;
+    while ((opt = getopt_long(argc, argv, "ah", longopts, nullptr)) != -1) {
+        switch (opt) {
+            case 'a':
+                show_table = true;
+                break;
+            case 'h':
+                usage(EXIT_SUCCESS);
+            default:
+                usage(EXIT_FAILURE);
+        }
+    }
+
+    pid_t pid = -1;
+    if (optind < argc) {
+        if (show_table) {
+            fprintf(stderr, "Invalid arguments: -a does not need arguments\n");
+            usage(EXIT_FAILURE);
+        }
+        if (optind != (argc - 1)) {
+            fprintf(stderr, "Invalid arguments - only one [PID] argument is allowed\n");
+            usage(EXIT_FAILURE);
+        }
+        pid = atoi(argv[optind]);
+        if (pid == 0) {
+            fprintf(stderr, "Invalid process id %s\n", argv[optind]);
             usage(EXIT_FAILURE);
         }
     }
 
-    if (show_all) {
-        if (!ReadDmaBufInfo(&bufs)) {
-            std::cerr << "debugfs entry for dmabuf not available, skipping" << std::endl;
-            bufs.clear();
-        }
-        std::unique_ptr<DIR, int (*)(DIR*)> dir(opendir("/proc"), closedir);
-        if (!dir) {
-            std::cerr << "Failed to open /proc directory" << std::endl;
+    std::vector<DmaBuffer> bufs;
+    if (pid != -1) {
+        if (!ReadDmaBufInfo(pid, &bufs)) {
+            fprintf(stderr, "Unable to read dmabuf info for %d\n", pid);
             exit(EXIT_FAILURE);
         }
-        struct dirent* dent;
-        while ((dent = readdir(dir.get()))) {
-            if (dent->d_type != DT_DIR) continue;
-
-            int matched = sscanf(dent->d_name, "%d", &pid);
-            if (matched != 1) {
-                continue;
-            }
-
-            if (!AppendDmaBufInfo(pid, &bufs)) {
-                std::cerr << "Unable to read dmabuf info for pid " << pid << std::endl;
-                exit(EXIT_FAILURE);
-            }
-        }
     } else {
-        if (!ReadDmaBufInfo(pid, &bufs)) {
-            std::cerr << "Unable to read dmabuf info" << std::endl;
-            exit(EXIT_FAILURE);
-        }
+        if (!ReadDmaBufs(&bufs)) exit(EXIT_FAILURE);
     }
 
-    PrintDmaBufInfo(bufs);
+    // Show the old dmabuf table, inode x process
+    if (show_table) {
+        PrintDmaBufTable(bufs);
+        return 0;
+    }
+
+    PrintDmaBufPerProcess(bufs);
+
     return 0;
 }