summaryrefslogtreecommitdiffstats
path: root/toolkit/components/processtools/ProcInfo_linux.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /toolkit/components/processtools/ProcInfo_linux.cpp
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/processtools/ProcInfo_linux.cpp')
-rw-r--r--toolkit/components/processtools/ProcInfo_linux.cpp344
1 files changed, 344 insertions, 0 deletions
diff --git a/toolkit/components/processtools/ProcInfo_linux.cpp b/toolkit/components/processtools/ProcInfo_linux.cpp
new file mode 100644
index 0000000000..51aa35c62e
--- /dev/null
+++ b/toolkit/components/processtools/ProcInfo_linux.cpp
@@ -0,0 +1,344 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ProcInfo.h"
+#include "mozilla/ProcInfo_linux.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/Logging.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/ipc/GeckoChildProcessHost.h"
+#include "nsMemoryReporterManager.h"
+#include "nsWhitespaceTokenizer.h"
+
+#include <cstdio>
+#include <cstring>
+#include <unistd.h>
+#include <dirent.h>
+
+#define NANOPERSEC 1000000000.
+
+namespace mozilla {
+
+int GetCycleTimeFrequencyMHz() { return 0; }
+
+// StatReader can parse and tokenize a POSIX stat file.
+// see http://man7.org/linux/man-pages/man5/proc.5.html
+//
+// Its usage is quite simple:
+//
+// StatReader reader(pid);
+// ProcInfo info;
+// rv = reader.ParseProc(info);
+// if (NS_FAILED(rv)) {
+// // the reading of the file or its parsing failed.
+// }
+//
+class StatReader {
+ public:
+ explicit StatReader(const base::ProcessId aPid)
+ : mPid(aPid), mMaxIndex(15), mTicksPerSec(sysconf(_SC_CLK_TCK)) {}
+
+ nsresult ParseProc(ProcInfo& aInfo) {
+ nsAutoString fileContent;
+ nsresult rv = ReadFile(fileContent);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // We first extract the file or thread name
+ int32_t startPos = fileContent.RFindChar('(');
+ if (startPos == -1) {
+ return NS_ERROR_FAILURE;
+ }
+ int32_t endPos = fileContent.RFindChar(')');
+ if (endPos == -1) {
+ return NS_ERROR_FAILURE;
+ }
+ int32_t len = endPos - (startPos + 1);
+ mName.Assign(Substring(fileContent, startPos + 1, len));
+
+ // now we can use the tokenizer for the rest of the file
+ nsWhitespaceTokenizer tokenizer(Substring(fileContent, endPos + 2));
+ int32_t index = 2; // starting at third field
+ while (tokenizer.hasMoreTokens() && index < mMaxIndex) {
+ const nsAString& token = tokenizer.nextToken();
+ rv = UseToken(index, token, aInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+ index++;
+ }
+ return NS_OK;
+ }
+
+ protected:
+ // Called for each token found in the stat file.
+ nsresult UseToken(int32_t aIndex, const nsAString& aToken, ProcInfo& aInfo) {
+ // We're using a subset of what stat has to offer for now.
+ nsresult rv = NS_OK;
+ // see the proc documentation for fields index references.
+ switch (aIndex) {
+ case 13:
+ // Amount of time that this process has been scheduled
+ // in user mode, measured in clock ticks
+ aInfo.cpuTime += GetCPUTime(aToken, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ case 14:
+ // Amount of time that this process has been scheduled
+ // in kernel mode, measured in clock ticks
+ aInfo.cpuTime += GetCPUTime(aToken, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ }
+ return rv;
+ }
+
+ // Converts a token into a int64_t
+ uint64_t Get64Value(const nsAString& aToken, nsresult* aRv) {
+ // We can't use aToken.ToInteger64() since it returns a signed 64.
+ // and that can result into an overflow.
+ nsresult rv = NS_OK;
+ uint64_t out = 0;
+ if (sscanf(NS_ConvertUTF16toUTF8(aToken).get(), "%" PRIu64, &out) == 0) {
+ rv = NS_ERROR_FAILURE;
+ }
+ *aRv = rv;
+ return out;
+ }
+
+ // Converts a token into CPU time in nanoseconds.
+ uint64_t GetCPUTime(const nsAString& aToken, nsresult* aRv) {
+ nsresult rv;
+ uint64_t value = Get64Value(aToken, &rv);
+ *aRv = rv;
+ if (NS_FAILED(rv)) {
+ return 0;
+ }
+ if (value) {
+ value = (value * NANOPERSEC) / mTicksPerSec;
+ }
+ return value;
+ }
+
+ base::ProcessId mPid;
+ int32_t mMaxIndex;
+ nsCString mFilepath;
+ nsString mName;
+
+ private:
+ // Reads the stat file and puts its content in a nsString.
+ nsresult ReadFile(nsAutoString& aFileContent) {
+ if (mFilepath.IsEmpty()) {
+ if (mPid == 0) {
+ mFilepath.AssignLiteral("/proc/self/stat");
+ } else {
+ mFilepath.AppendPrintf("/proc/%u/stat", unsigned(mPid));
+ }
+ }
+ FILE* fstat = fopen(mFilepath.get(), "r");
+ if (!fstat) {
+ return NS_ERROR_FAILURE;
+ }
+ // /proc is a virtual file system and all files are
+ // of size 0, so GetFileSize() and related functions will
+ // return 0 - so the way to read the file is to fill a buffer
+ // of an arbitrary big size and look for the end of line char.
+ char buffer[2048];
+ char* end;
+ char* start = fgets(buffer, 2048, fstat);
+ fclose(fstat);
+ if (start == nullptr) {
+ return NS_ERROR_FAILURE;
+ }
+ // let's find the end
+ end = strchr(buffer, '\n');
+ if (!end) {
+ return NS_ERROR_FAILURE;
+ }
+ aFileContent.AssignASCII(buffer, size_t(end - start));
+ return NS_OK;
+ }
+
+ int64_t mTicksPerSec;
+};
+
+// Threads have the same stat file. The only difference is its path
+// and we're getting less info in the ThreadInfo structure.
+class ThreadInfoReader final : public StatReader {
+ public:
+ ThreadInfoReader(const base::ProcessId aPid, const base::ProcessId aTid)
+ : StatReader(aPid) {
+ mFilepath.AppendPrintf("/proc/%u/task/%u/stat", unsigned(aPid),
+ unsigned(aTid));
+ }
+
+ nsresult ParseThread(ThreadInfo& aInfo) {
+ ProcInfo info;
+ nsresult rv = StatReader::ParseProc(info);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Copying over the data we got from StatReader::ParseProc()
+ aInfo.cpuTime = info.cpuTime;
+ aInfo.name.Assign(mName);
+ return NS_OK;
+ }
+};
+
+nsresult GetCpuTimeSinceProcessStartInMs(uint64_t* aResult) {
+ timespec t;
+ if (clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &t) == 0) {
+ uint64_t cpuTime =
+ uint64_t(t.tv_sec) * 1'000'000'000u + uint64_t(t.tv_nsec);
+ *aResult = cpuTime / PR_NSEC_PER_MSEC;
+ return NS_OK;
+ }
+
+ StatReader reader(0);
+ ProcInfo info;
+ nsresult rv = reader.ParseProc(info);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ *aResult = info.cpuTime / PR_NSEC_PER_MSEC;
+ return NS_OK;
+}
+
+nsresult GetGpuTimeSinceProcessStartInMs(uint64_t* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+ProcInfoPromise::ResolveOrRejectValue GetProcInfoSync(
+ nsTArray<ProcInfoRequest>&& aRequests) {
+ ProcInfoPromise::ResolveOrRejectValue result;
+
+ HashMap<base::ProcessId, ProcInfo> gathered;
+ if (!gathered.reserve(aRequests.Length())) {
+ result.SetReject(NS_ERROR_OUT_OF_MEMORY);
+ return result;
+ }
+ for (const auto& request : aRequests) {
+ ProcInfo info;
+
+ timespec t;
+ clockid_t clockid = MAKE_PROCESS_CPUCLOCK(request.pid, CPUCLOCK_SCHED);
+ if (clock_gettime(clockid, &t) == 0) {
+ info.cpuTime = uint64_t(t.tv_sec) * 1'000'000'000u + uint64_t(t.tv_nsec);
+ } else {
+ // Fallback to parsing /proc/<pid>/stat
+ StatReader reader(request.pid);
+ nsresult rv = reader.ParseProc(info);
+ if (NS_FAILED(rv)) {
+ // Can't read data for this proc.
+ // Probably either a sandboxing issue or a race condition, e.g.
+ // the process has been just been killed. Regardless, skip process.
+ continue;
+ }
+ }
+
+ // The 'Memory' value displayed in the system monitor is resident -
+ // shared. statm contains more fields, but we're only interested in
+ // the first three.
+ static const int MAX_FIELD = 3;
+ size_t VmSize, resident, shared;
+ info.memory = 0;
+ FILE* f = fopen(nsPrintfCString("/proc/%u/statm", request.pid).get(), "r");
+ if (f) {
+ int nread = fscanf(f, "%zu %zu %zu", &VmSize, &resident, &shared);
+ fclose(f);
+ if (nread == MAX_FIELD) {
+ info.memory = (resident - shared) * getpagesize();
+ }
+ }
+
+ // Extra info
+ info.pid = request.pid;
+ info.childId = request.childId;
+ info.type = request.processType;
+ info.origin = request.origin;
+ info.windows = std::move(request.windowInfo);
+ info.utilityActors = std::move(request.utilityInfo);
+
+ // Let's look at the threads
+ nsCString taskPath;
+ taskPath.AppendPrintf("/proc/%u/task", unsigned(request.pid));
+ DIR* dirHandle = opendir(taskPath.get());
+ if (!dirHandle) {
+ // For some reason, we have no data on the threads for this process.
+ // Most likely reason is that we have just lost a race condition and
+ // the process is dead.
+ // Let's stop here and ignore the entire process.
+ continue;
+ }
+ auto cleanup = mozilla::MakeScopeExit([&] { closedir(dirHandle); });
+
+ // If we can't read some thread info, we ignore that thread.
+ dirent* entry;
+ while ((entry = readdir(dirHandle)) != nullptr) {
+ if (entry->d_name[0] == '.') {
+ continue;
+ }
+ nsAutoCString entryName(entry->d_name);
+ nsresult rv;
+ int32_t tid = entryName.ToInteger(&rv);
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ ThreadInfo threadInfo;
+ threadInfo.tid = tid;
+
+ timespec ts;
+ if (clock_gettime(MAKE_THREAD_CPUCLOCK(tid, CPUCLOCK_SCHED), &ts) == 0) {
+ threadInfo.cpuTime =
+ uint64_t(ts.tv_sec) * 1'000'000'000u + uint64_t(ts.tv_nsec);
+
+ nsCString path;
+ path.AppendPrintf("/proc/%u/task/%u/comm", unsigned(request.pid),
+ unsigned(tid));
+ FILE* fstat = fopen(path.get(), "r");
+ if (fstat) {
+ // /proc is a virtual file system and all files are
+ // of size 0, so GetFileSize() and related functions will
+ // return 0 - so the way to read the file is to fill a buffer
+ // of an arbitrary big size and look for the end of line char.
+ // The size of the buffer needs to be as least 16, which is the
+ // value of TASK_COMM_LEN in the Linux kernel.
+ char buffer[32];
+ char* start = fgets(buffer, sizeof(buffer), fstat);
+ fclose(fstat);
+ if (start) {
+ // The thread name should always be smaller than our buffer,
+ // so we should find a newline character.
+ char* end = strchr(buffer, '\n');
+ if (end) {
+ threadInfo.name.AssignASCII(buffer, size_t(end - start));
+ info.threads.AppendElement(threadInfo);
+ continue;
+ }
+ }
+ }
+ }
+
+ // Fallback to parsing /proc/<pid>/task/<tid>/stat
+ // This is needed for child processes, as access to the per-thread
+ // CPU clock is restricted to the process owning the thread.
+ ThreadInfoReader reader(request.pid, tid);
+ rv = reader.ParseThread(threadInfo);
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+ info.threads.AppendElement(threadInfo);
+ }
+
+ if (!gathered.put(request.pid, std::move(info))) {
+ result.SetReject(NS_ERROR_OUT_OF_MEMORY);
+ return result;
+ }
+ }
+
+ // ... and we're done!
+ result.SetResolve(std::move(gathered));
+ return result;
+}
+
+} // namespace mozilla