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;
+};