Add logcat process name resolution (proto only)

When outputting protobuffer, logcat now has the option to also resolve
pid name. It does so by first looking into /proc/pid/cmdline and then
, if that failed, in /proc/pid/comm.

To limit I/O impact, process names are cached in memory. The cache is
made of a maximum of 100 entries, containing roughly 30 bytes each. The
eviction policy is LRU.

Test: lru_tests.cpp
Bug: 257127748
Change-Id: I2ff4e74ca8e6f6bc26d7e31dc8a185bb0aa15b96
diff --git a/logcat/Android.bp b/logcat/Android.bp
index 9d0959f..dda61ee 100644
--- a/logcat/Android.bp
+++ b/logcat/Android.bp
@@ -41,6 +41,7 @@
     ],
     shared_libs: [
         "libbase",
+        "libutils",
         "libprocessgroup",
         "libprotobuf-cpp-lite",
     ],
@@ -49,6 +50,7 @@
     srcs: [
         "logcat.cpp",
         "logcat.proto",
+        "process_names.cpp",
     ],
 }
 
diff --git a/logcat/logcat.cpp b/logcat/logcat.cpp
index bd2c015..be37a68 100644
--- a/logcat/logcat.cpp
+++ b/logcat/logcat.cpp
@@ -58,6 +58,7 @@
 #include <processgroup/sched_policy.h>
 #include <system/thread_defs.h>
 #include "logcat.pb.h"
+#include "process_names.h"
 
 using com::android::logcat::proto::LogcatEntryProto;
 using com::android::logcat::proto::LogcatPriorityProto;
@@ -131,6 +132,8 @@
     bool printed_start_[LOG_ID_MAX] = {};
 
     bool debug_ = false;
+
+    ProcessNames process_names_;
 };
 
 static void pinLogFile(int fd, size_t sizeKB) {
@@ -289,6 +292,10 @@
     proto.set_tid(entry.tid);
     proto.set_tag(entry.tag, entry.tagLen);
     proto.set_message(entry.message, entry.messageLen);
+    const std::string name = process_names_.Get(entry.pid);
+    if (!name.empty()) {
+        proto.set_process_name(name);
+    }
 
     // Serialize
     std::string data;
diff --git a/logcat/logcat.proto b/logcat/logcat.proto
index d07d6fd..ecbfeee 100644
--- a/logcat/logcat.proto
+++ b/logcat/logcat.proto
@@ -25,4 +25,5 @@
   uint64 tid = 6;
   bytes tag = 7;
   bytes message = 8;
+  optional string process_name = 9;
 }
\ No newline at end of file
diff --git a/logcat/process_names.cpp b/logcat/process_names.cpp
new file mode 100644
index 0000000..8971809
--- /dev/null
+++ b/logcat/process_names.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "process_names.h"
+
+#include "android-base/file.h"
+
+const std::string ProcessNames::ReadCmdline(uint64_t pid) {
+    const std::string path = std::string("/proc/") + std::to_string(pid) + "/cmdline";
+
+    std::string cmdline;
+    if (!android::base::ReadFileToString(path, &cmdline)) {
+        return "";
+    }
+
+    // We need to remove anything that would be part of an absolute path for the executable
+    // but also the parameters. e.g.:
+    // Input : /path/to/myProgram -D --ooo
+    // Output: myProgram
+    return android::base::Basename(cmdline.c_str());
+}
+
+const std::string ProcessNames::ReadComm(uint64_t pid) {
+    const std::string path = std::string("/proc/") + std::to_string(pid) + "/comm";
+    std::string cmdline;
+    bool success = android::base::ReadFileToString(path, &cmdline);
+    if (!success) {
+        return "";
+    }
+    return cmdline;
+}
+
+const std::string ProcessNames::Resolve(uint64_t pid) {
+    std::string name = ReadCmdline(pid);
+    if (!name.empty()) {
+        return name;
+    }
+
+    // Kernel threads do not have anything in /proc/PID/cmdline. e.g.:
+    // migration/0
+    // cpuhp/0
+    // kworker/7:1-events
+    //
+    // To still have a somewhat relevant name, we check /proc/PID/comm, even though
+    // the max length is 16 characters.
+    name = ReadComm(pid);
+    if (!name.empty()) {
+        return name;
+    }
+
+    return "";
+}
+
+std::string ProcessNames::Get(uint64_t pid) {
+    // Cache hit!
+    const std::string& cached = cache.get(pid);
+    if (!cached.empty()) {
+        return cached;
+    }
+
+    // Cache miss!
+    std::string name = Resolve(pid);
+    cache.put(pid, name);
+    return name;
+}
diff --git a/logcat/process_names.h b/logcat/process_names.h
new file mode 100644
index 0000000..50a7044
--- /dev/null
+++ b/logcat/process_names.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <string>
+
+#include "utils/LruCache.h"
+
+// An interface to associate pid to a name. Implemented by looking up /proc/PID.
+// To lower syscall impact, results are cached.
+class ProcessNames {
+  public:
+    ProcessNames() : cache(kMaxCacheEntries) {}
+
+    ~ProcessNames() = default;
+
+    // Returns the executable name or in the case of an app, the package name associated
+    // with a process pid.
+    std::string Get(uint64_t pid);
+
+  private:
+    const std::string ReadCmdline(uint64_t pid);
+    const std::string ReadComm(uint64_t pid);
+    const std::string Resolve(uint64_t pid);
+
+    // kMaxCacheEntries should be picked to keep the memory footprint low (1) and yield a
+    // high cache hit rate (2).
+    // 1. We cache executable name or package name, which account for roughly 20 characters
+    //    each. Using a 100 figure results in 2 KiB for cache storage.
+    // 2. Difficult to tune since it depends on how many process are alive and how much they
+    //    generate towards liblob. From manual testing, 100 entries resulted in 99% cache hit
+    //    with AOSP 34, right after boot, and one app active. We could monitor this value by
+    //    augmenting the protobuffer and have a cache hit boolean to generate a cache hit figure
+    //    on the workstation.
+    static const uint64_t kMaxCacheEntries = 100;
+    android::LruCache<uint64_t, std::string> cache;
+};