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"
21 #define NANOPERSEC 1000000000.
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);
34 // rv = reader.ParseProc(info);
35 // if (NS_FAILED(rv)) {
36 // // the reading of the file or its parsing failed.
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('(');
51 return NS_ERROR_FAILURE
;
53 int32_t endPos
= fileContent
.RFindChar(')');
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
);
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.
77 // see the proc documentation for fields index references.
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
);
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
);
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.
101 if (sscanf(NS_ConvertUTF16toUTF8(aToken
).get(), "%" PRIu64
, &out
) == 0) {
102 rv
= NS_ERROR_FAILURE
;
108 // Converts a token into CPU time in nanoseconds.
109 uint64_t GetCPUTime(const nsAString
& aToken
, nsresult
* aRv
) {
111 uint64_t value
= Get64Value(aToken
, &rv
);
117 value
= (value
* NANOPERSEC
) / mTicksPerSec
;
122 base::ProcessId mPid
;
128 // Reads the stat file and puts its content in a nsString.
129 nsresult
ReadFile(nsAutoString
& aFileContent
) {
130 if (mFilepath
.IsEmpty()) {
132 mFilepath
.AssignLiteral("/proc/self/stat");
134 mFilepath
.AppendPrintf("/proc/%u/stat", unsigned(mPid
));
137 FILE* fstat
= fopen(mFilepath
.get(), "r");
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.
147 char* start
= fgets(buffer
, 2048, fstat
);
149 if (start
== nullptr) {
150 return NS_ERROR_FAILURE
;
152 // let's find the end
153 end
= strchr(buffer
, '\n');
155 return NS_ERROR_FAILURE
;
157 aFileContent
.AssignASCII(buffer
, size_t(end
- start
));
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
{
168 ThreadInfoReader(const base::ProcessId aPid
, const base::ProcessId aTid
)
170 mFilepath
.AppendPrintf("/proc/%u/task/%u/stat", unsigned(aPid
),
174 nsresult
ParseThread(ThreadInfo
& aInfo
) {
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
);
186 nsresult
GetCpuTimeSinceProcessStartInMs(uint64_t* aResult
) {
188 if (clock_gettime(CLOCK_PROCESS_CPUTIME_ID
, &t
) == 0) {
190 uint64_t(t
.tv_sec
) * 1'000'000'000u + uint64_t(t
.tv_nsec
);
191 *aResult
= cpuTime
/ PR_NSEC_PER_MSEC
;
195 StatReader
reader(0);
197 nsresult rv
= reader
.ParseProc(info
);
202 *aResult
= info
.cpuTime
/ PR_NSEC_PER_MSEC
;
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
);
219 for (const auto& request
: aRequests
) {
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
);
227 // Fallback to parsing /proc/<pid>/stat
228 StatReader
reader(request
.pid
);
229 nsresult rv
= reader
.ParseProc(info
);
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.
238 // The 'Memory' value displayed in the system monitor is resident -
239 // shared. statm contains more fields, but we're only interested in
241 static const int MAX_FIELD
= 3;
242 size_t VmSize
, resident
, shared
;
244 FILE* f
= fopen(nsPrintfCString("/proc/%u/statm", request
.pid
).get(), "r");
246 int nread
= fscanf(f
, "%zu %zu %zu", &VmSize
, &resident
, &shared
);
248 if (nread
== MAX_FIELD
) {
249 info
.memory
= (resident
- shared
) * getpagesize();
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
263 taskPath
.AppendPrintf("/proc/%u/task", unsigned(request
.pid
));
264 DIR* dirHandle
= opendir(taskPath
.get());
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.
272 auto cleanup
= mozilla::MakeScopeExit([&] { closedir(dirHandle
); });
274 // If we can't read some thread info, we ignore that thread.
276 while ((entry
= readdir(dirHandle
)) != nullptr) {
277 if (entry
->d_name
[0] == '.') {
280 nsAutoCString
entryName(entry
->d_name
);
282 int32_t tid
= entryName
.ToInteger(&rv
);
287 ThreadInfo threadInfo
;
288 threadInfo
.tid
= tid
;
291 if (clock_gettime(MAKE_THREAD_CPUCLOCK(tid
, CPUCLOCK_SCHED
), &ts
) == 0) {
293 uint64_t(ts
.tv_sec
) * 1'000'000'000u + uint64_t(ts
.tv_nsec
);
296 path
.AppendPrintf("/proc/%u/task/%u/comm", unsigned(request
.pid
),
298 FILE* fstat
= fopen(path
.get(), "r");
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.
307 char* start
= fgets(buffer
, sizeof(buffer
), fstat
);
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');
314 threadInfo
.name
.AssignASCII(buffer
, size_t(end
- start
));
315 info
.threads
.AppendElement(threadInfo
);
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
);
330 info
.threads
.AppendElement(threadInfo
);
333 if (!gathered
.put(request
.pid
, std::move(info
))) {
334 result
.SetReject(NS_ERROR_OUT_OF_MEMORY
);
339 // ... and we're done!
340 result
.SetResolve(std::move(gathered
));
344 } // namespace mozilla