Bug 1933479 - Add tab close button on hover to vertical tabs when sidebar is collapse...
[gecko.git] / toolkit / components / processtools / ProcInfo_linux.cpp
blob51aa35c62e056e32bbf61bef1fdb8be90b123d7c
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/ProcInfo.h"
8 #include "mozilla/ProcInfo_linux.h"
9 #include "mozilla/Sprintf.h"
10 #include "mozilla/Logging.h"
11 #include "mozilla/ScopeExit.h"
12 #include "mozilla/ipc/GeckoChildProcessHost.h"
13 #include "nsMemoryReporterManager.h"
14 #include "nsWhitespaceTokenizer.h"
16 #include <cstdio>
17 #include <cstring>
18 #include <unistd.h>
19 #include <dirent.h>
21 #define NANOPERSEC 1000000000.
23 namespace mozilla {
25 int GetCycleTimeFrequencyMHz() { return 0; }
27 // StatReader can parse and tokenize a POSIX stat file.
28 // see http://man7.org/linux/man-pages/man5/proc.5.html
30 // Its usage is quite simple:
32 // StatReader reader(pid);
33 // ProcInfo info;
34 // rv = reader.ParseProc(info);
35 // if (NS_FAILED(rv)) {
36 // // the reading of the file or its parsing failed.
37 // }
39 class StatReader {
40 public:
41 explicit StatReader(const base::ProcessId aPid)
42 : mPid(aPid), mMaxIndex(15), mTicksPerSec(sysconf(_SC_CLK_TCK)) {}
44 nsresult ParseProc(ProcInfo& aInfo) {
45 nsAutoString fileContent;
46 nsresult rv = ReadFile(fileContent);
47 NS_ENSURE_SUCCESS(rv, rv);
48 // We first extract the file or thread name
49 int32_t startPos = fileContent.RFindChar('(');
50 if (startPos == -1) {
51 return NS_ERROR_FAILURE;
53 int32_t endPos = fileContent.RFindChar(')');
54 if (endPos == -1) {
55 return NS_ERROR_FAILURE;
57 int32_t len = endPos - (startPos + 1);
58 mName.Assign(Substring(fileContent, startPos + 1, len));
60 // now we can use the tokenizer for the rest of the file
61 nsWhitespaceTokenizer tokenizer(Substring(fileContent, endPos + 2));
62 int32_t index = 2; // starting at third field
63 while (tokenizer.hasMoreTokens() && index < mMaxIndex) {
64 const nsAString& token = tokenizer.nextToken();
65 rv = UseToken(index, token, aInfo);
66 NS_ENSURE_SUCCESS(rv, rv);
67 index++;
69 return NS_OK;
72 protected:
73 // Called for each token found in the stat file.
74 nsresult UseToken(int32_t aIndex, const nsAString& aToken, ProcInfo& aInfo) {
75 // We're using a subset of what stat has to offer for now.
76 nsresult rv = NS_OK;
77 // see the proc documentation for fields index references.
78 switch (aIndex) {
79 case 13:
80 // Amount of time that this process has been scheduled
81 // in user mode, measured in clock ticks
82 aInfo.cpuTime += GetCPUTime(aToken, &rv);
83 NS_ENSURE_SUCCESS(rv, rv);
84 break;
85 case 14:
86 // Amount of time that this process has been scheduled
87 // in kernel mode, measured in clock ticks
88 aInfo.cpuTime += GetCPUTime(aToken, &rv);
89 NS_ENSURE_SUCCESS(rv, rv);
90 break;
92 return rv;
95 // Converts a token into a int64_t
96 uint64_t Get64Value(const nsAString& aToken, nsresult* aRv) {
97 // We can't use aToken.ToInteger64() since it returns a signed 64.
98 // and that can result into an overflow.
99 nsresult rv = NS_OK;
100 uint64_t out = 0;
101 if (sscanf(NS_ConvertUTF16toUTF8(aToken).get(), "%" PRIu64, &out) == 0) {
102 rv = NS_ERROR_FAILURE;
104 *aRv = rv;
105 return out;
108 // Converts a token into CPU time in nanoseconds.
109 uint64_t GetCPUTime(const nsAString& aToken, nsresult* aRv) {
110 nsresult rv;
111 uint64_t value = Get64Value(aToken, &rv);
112 *aRv = rv;
113 if (NS_FAILED(rv)) {
114 return 0;
116 if (value) {
117 value = (value * NANOPERSEC) / mTicksPerSec;
119 return value;
122 base::ProcessId mPid;
123 int32_t mMaxIndex;
124 nsCString mFilepath;
125 nsString mName;
127 private:
128 // Reads the stat file and puts its content in a nsString.
129 nsresult ReadFile(nsAutoString& aFileContent) {
130 if (mFilepath.IsEmpty()) {
131 if (mPid == 0) {
132 mFilepath.AssignLiteral("/proc/self/stat");
133 } else {
134 mFilepath.AppendPrintf("/proc/%u/stat", unsigned(mPid));
137 FILE* fstat = fopen(mFilepath.get(), "r");
138 if (!fstat) {
139 return NS_ERROR_FAILURE;
141 // /proc is a virtual file system and all files are
142 // of size 0, so GetFileSize() and related functions will
143 // return 0 - so the way to read the file is to fill a buffer
144 // of an arbitrary big size and look for the end of line char.
145 char buffer[2048];
146 char* end;
147 char* start = fgets(buffer, 2048, fstat);
148 fclose(fstat);
149 if (start == nullptr) {
150 return NS_ERROR_FAILURE;
152 // let's find the end
153 end = strchr(buffer, '\n');
154 if (!end) {
155 return NS_ERROR_FAILURE;
157 aFileContent.AssignASCII(buffer, size_t(end - start));
158 return NS_OK;
161 int64_t mTicksPerSec;
164 // Threads have the same stat file. The only difference is its path
165 // and we're getting less info in the ThreadInfo structure.
166 class ThreadInfoReader final : public StatReader {
167 public:
168 ThreadInfoReader(const base::ProcessId aPid, const base::ProcessId aTid)
169 : StatReader(aPid) {
170 mFilepath.AppendPrintf("/proc/%u/task/%u/stat", unsigned(aPid),
171 unsigned(aTid));
174 nsresult ParseThread(ThreadInfo& aInfo) {
175 ProcInfo info;
176 nsresult rv = StatReader::ParseProc(info);
177 NS_ENSURE_SUCCESS(rv, rv);
179 // Copying over the data we got from StatReader::ParseProc()
180 aInfo.cpuTime = info.cpuTime;
181 aInfo.name.Assign(mName);
182 return NS_OK;
186 nsresult GetCpuTimeSinceProcessStartInMs(uint64_t* aResult) {
187 timespec t;
188 if (clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &t) == 0) {
189 uint64_t cpuTime =
190 uint64_t(t.tv_sec) * 1'000'000'000u + uint64_t(t.tv_nsec);
191 *aResult = cpuTime / PR_NSEC_PER_MSEC;
192 return NS_OK;
195 StatReader reader(0);
196 ProcInfo info;
197 nsresult rv = reader.ParseProc(info);
198 if (NS_FAILED(rv)) {
199 return rv;
202 *aResult = info.cpuTime / PR_NSEC_PER_MSEC;
203 return NS_OK;
206 nsresult GetGpuTimeSinceProcessStartInMs(uint64_t* aResult) {
207 return NS_ERROR_NOT_IMPLEMENTED;
210 ProcInfoPromise::ResolveOrRejectValue GetProcInfoSync(
211 nsTArray<ProcInfoRequest>&& aRequests) {
212 ProcInfoPromise::ResolveOrRejectValue result;
214 HashMap<base::ProcessId, ProcInfo> gathered;
215 if (!gathered.reserve(aRequests.Length())) {
216 result.SetReject(NS_ERROR_OUT_OF_MEMORY);
217 return result;
219 for (const auto& request : aRequests) {
220 ProcInfo info;
222 timespec t;
223 clockid_t clockid = MAKE_PROCESS_CPUCLOCK(request.pid, CPUCLOCK_SCHED);
224 if (clock_gettime(clockid, &t) == 0) {
225 info.cpuTime = uint64_t(t.tv_sec) * 1'000'000'000u + uint64_t(t.tv_nsec);
226 } else {
227 // Fallback to parsing /proc/<pid>/stat
228 StatReader reader(request.pid);
229 nsresult rv = reader.ParseProc(info);
230 if (NS_FAILED(rv)) {
231 // Can't read data for this proc.
232 // Probably either a sandboxing issue or a race condition, e.g.
233 // the process has been just been killed. Regardless, skip process.
234 continue;
238 // The 'Memory' value displayed in the system monitor is resident -
239 // shared. statm contains more fields, but we're only interested in
240 // the first three.
241 static const int MAX_FIELD = 3;
242 size_t VmSize, resident, shared;
243 info.memory = 0;
244 FILE* f = fopen(nsPrintfCString("/proc/%u/statm", request.pid).get(), "r");
245 if (f) {
246 int nread = fscanf(f, "%zu %zu %zu", &VmSize, &resident, &shared);
247 fclose(f);
248 if (nread == MAX_FIELD) {
249 info.memory = (resident - shared) * getpagesize();
253 // Extra info
254 info.pid = request.pid;
255 info.childId = request.childId;
256 info.type = request.processType;
257 info.origin = request.origin;
258 info.windows = std::move(request.windowInfo);
259 info.utilityActors = std::move(request.utilityInfo);
261 // Let's look at the threads
262 nsCString taskPath;
263 taskPath.AppendPrintf("/proc/%u/task", unsigned(request.pid));
264 DIR* dirHandle = opendir(taskPath.get());
265 if (!dirHandle) {
266 // For some reason, we have no data on the threads for this process.
267 // Most likely reason is that we have just lost a race condition and
268 // the process is dead.
269 // Let's stop here and ignore the entire process.
270 continue;
272 auto cleanup = mozilla::MakeScopeExit([&] { closedir(dirHandle); });
274 // If we can't read some thread info, we ignore that thread.
275 dirent* entry;
276 while ((entry = readdir(dirHandle)) != nullptr) {
277 if (entry->d_name[0] == '.') {
278 continue;
280 nsAutoCString entryName(entry->d_name);
281 nsresult rv;
282 int32_t tid = entryName.ToInteger(&rv);
283 if (NS_FAILED(rv)) {
284 continue;
287 ThreadInfo threadInfo;
288 threadInfo.tid = tid;
290 timespec ts;
291 if (clock_gettime(MAKE_THREAD_CPUCLOCK(tid, CPUCLOCK_SCHED), &ts) == 0) {
292 threadInfo.cpuTime =
293 uint64_t(ts.tv_sec) * 1'000'000'000u + uint64_t(ts.tv_nsec);
295 nsCString path;
296 path.AppendPrintf("/proc/%u/task/%u/comm", unsigned(request.pid),
297 unsigned(tid));
298 FILE* fstat = fopen(path.get(), "r");
299 if (fstat) {
300 // /proc is a virtual file system and all files are
301 // of size 0, so GetFileSize() and related functions will
302 // return 0 - so the way to read the file is to fill a buffer
303 // of an arbitrary big size and look for the end of line char.
304 // The size of the buffer needs to be as least 16, which is the
305 // value of TASK_COMM_LEN in the Linux kernel.
306 char buffer[32];
307 char* start = fgets(buffer, sizeof(buffer), fstat);
308 fclose(fstat);
309 if (start) {
310 // The thread name should always be smaller than our buffer,
311 // so we should find a newline character.
312 char* end = strchr(buffer, '\n');
313 if (end) {
314 threadInfo.name.AssignASCII(buffer, size_t(end - start));
315 info.threads.AppendElement(threadInfo);
316 continue;
322 // Fallback to parsing /proc/<pid>/task/<tid>/stat
323 // This is needed for child processes, as access to the per-thread
324 // CPU clock is restricted to the process owning the thread.
325 ThreadInfoReader reader(request.pid, tid);
326 rv = reader.ParseThread(threadInfo);
327 if (NS_FAILED(rv)) {
328 continue;
330 info.threads.AppendElement(threadInfo);
333 if (!gathered.put(request.pid, std::move(info))) {
334 result.SetReject(NS_ERROR_OUT_OF_MEMORY);
335 return result;
339 // ... and we're done!
340 result.SetResolve(std::move(gathered));
341 return result;
344 } // namespace mozilla