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/ipc/GeckoChildProcessHost.h"
9 #include "mozilla/SSE.h"
10 #include "gfxWindowsPlatform.h"
11 #include "nsMemoryReporterManager.h"
12 #include "nsWindowsHelpers.h"
16 #include <xpcpublic.h>
18 #ifndef STATUS_INFO_LENGTH_MISMATCH
19 # define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
22 #define PR_USEC_PER_NSEC 1000L
24 typedef HRESULT(WINAPI
* GETTHREADDESCRIPTION
)(HANDLE hThread
,
25 PWSTR
* threadDescription
);
29 static uint64_t ToNanoSeconds(const FILETIME
& aFileTime
) {
30 // FILETIME values are 100-nanoseconds units, converting
31 ULARGE_INTEGER usec
= {{aFileTime
.dwLowDateTime
, aFileTime
.dwHighDateTime
}};
32 return usec
.QuadPart
* 100;
35 int GetCpuFrequencyMHz() {
36 static const int frequency
= []() {
37 // Get the nominal CPU frequency.
39 static const WCHAR keyName
[] =
40 L
"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0";
42 if (RegOpenKeyEx(HKEY_LOCAL_MACHINE
, keyName
, 0, KEY_QUERY_VALUE
, &key
) ==
47 if (RegQueryValueEx(key
, L
"~Mhz", 0, 0, reinterpret_cast<LPBYTE
>(&data
),
48 &len
) == ERROR_SUCCESS
) {
49 return static_cast<int>(data
);
59 int GetCycleTimeFrequencyMHz() {
60 static const int frequency
= []() {
61 // Having a constant TSC is required to convert cycle time to actual time.
62 // In automation, having short CPU times reported as 0 is more of a problem
63 // than having an imprecise value. The fallback method can't report CPU
65 if (!mozilla::has_constant_tsc() && !xpc::IsInAutomation()) {
69 return GetCpuFrequencyMHz();
75 nsresult
GetCpuTimeSinceProcessStartInMs(uint64_t* aResult
) {
76 int frequencyInMHz
= GetCycleTimeFrequencyMHz();
78 uint64_t cpuCycleCount
;
79 if (!QueryProcessCycleTime(::GetCurrentProcess(), &cpuCycleCount
)) {
80 return NS_ERROR_FAILURE
;
82 constexpr int HZ_PER_MHZ
= 1000000;
84 cpuCycleCount
/ (frequencyInMHz
* (HZ_PER_MHZ
/ PR_MSEC_PER_SEC
));
88 FILETIME createTime
, exitTime
, kernelTime
, userTime
;
89 if (!GetProcessTimes(::GetCurrentProcess(), &createTime
, &exitTime
,
90 &kernelTime
, &userTime
)) {
91 return NS_ERROR_FAILURE
;
94 (ToNanoSeconds(kernelTime
) + ToNanoSeconds(userTime
)) / PR_NSEC_PER_MSEC
;
98 nsresult
GetGpuTimeSinceProcessStartInMs(uint64_t* aResult
) {
99 return gfxWindowsPlatform::GetGpuTimeSinceProcessStartInMs(aResult
);
102 ProcInfoPromise::ResolveOrRejectValue
GetProcInfoSync(
103 nsTArray
<ProcInfoRequest
>&& aRequests
) {
104 ProcInfoPromise::ResolveOrRejectValue result
;
106 HashMap
<base::ProcessId
, ProcInfo
> gathered
;
107 if (!gathered
.reserve(aRequests
.Length())) {
108 result
.SetReject(NS_ERROR_OUT_OF_MEMORY
);
112 int frequencyInMHz
= GetCycleTimeFrequencyMHz();
114 // ---- Copying data on processes (minus threads).
116 for (const auto& request
: aRequests
) {
117 nsAutoHandle
handle(OpenProcess(PROCESS_QUERY_INFORMATION
| PROCESS_VM_READ
,
118 FALSE
, request
.pid
));
121 // Ignore process, it may have died.
125 uint64_t cpuCycleTime
;
126 if (!QueryProcessCycleTime(handle
.get(), &cpuCycleTime
)) {
127 // Ignore process, it may have died.
132 if (frequencyInMHz
) {
133 cpuTime
= cpuCycleTime
* PR_USEC_PER_NSEC
/ frequencyInMHz
;
135 FILETIME createTime
, exitTime
, kernelTime
, userTime
;
136 if (!GetProcessTimes(handle
.get(), &createTime
, &exitTime
, &kernelTime
,
138 // Ignore process, it may have died.
141 cpuTime
= ToNanoSeconds(kernelTime
) + ToNanoSeconds(userTime
);
144 PROCESS_MEMORY_COUNTERS_EX memoryCounters
;
145 if (!GetProcessMemoryInfo(handle
.get(),
146 (PPROCESS_MEMORY_COUNTERS
)&memoryCounters
,
147 sizeof(memoryCounters
))) {
148 // Ignore process, it may have died.
152 // Assumption: values of `pid` are distinct between processes,
153 // regardless of any race condition we might have stumbled upon. Even
154 // if it somehow could happen, in the worst case scenario, we might
155 // end up overwriting one process info and we might end up with too
156 // many threads attached to a process, as the data is not crucial, we
157 // do not need to defend against that (unlikely) scenario.
159 info
.pid
= request
.pid
;
160 info
.childId
= request
.childId
;
161 info
.type
= request
.processType
;
162 info
.origin
= request
.origin
;
163 info
.windows
= std::move(request
.windowInfo
);
164 info
.utilityActors
= std::move(request
.utilityInfo
);
165 info
.cpuTime
= cpuTime
;
166 info
.cpuCycleCount
= cpuCycleTime
;
167 info
.memory
= memoryCounters
.PrivateUsage
;
169 if (!gathered
.put(request
.pid
, std::move(info
))) {
170 result
.SetReject(NS_ERROR_OUT_OF_MEMORY
);
175 // ---- Add thread data to already-copied processes.
179 UniquePtr
<char[]> buf
;
180 ULONG bufLen
= 512u * 1024u;
182 // We must query for information in a loop, since we are effectively asking
183 // the kernel to take a snapshot of all the processes on the system;
184 // the size of the required buffer may fluctuate between successive calls.
186 // These allocations can be hundreds of megabytes on some computers, so
187 // we should use fallible new here.
188 buf
= MakeUniqueFallible
<char[]>(bufLen
);
190 result
.SetReject(NS_ERROR_OUT_OF_MEMORY
);
194 ntStatus
= ::NtQuerySystemInformation(SystemProcessInformation
, buf
.get(),
196 if (ntStatus
!= STATUS_INFO_LENGTH_MISMATCH
) {
200 // If we need another NtQuerySystemInformation call, allocate a
201 // slightly larger buffer than what would have been needed this time,
202 // to account for possible process or thread creations that might
203 // happen between our calls.
204 bufLen
+= 8u * 1024u;
206 if (!NT_SUCCESS(ntStatus
)) {
207 result
.SetReject(NS_ERROR_UNEXPECTED
);
211 // `GetThreadDescription` is available as of Windows 10.
212 // We attempt to import it dynamically, knowing that it
214 auto getThreadDescription
=
215 reinterpret_cast<GETTHREADDESCRIPTION
>(::GetProcAddress(
216 ::GetModuleHandleW(L
"Kernel32.dll"), "GetThreadDescription"));
218 PSYSTEM_PROCESS_INFORMATION processInfo
;
219 for (ULONG offset
= 0;; offset
+= processInfo
->NextEntryOffset
) {
220 MOZ_RELEASE_ASSERT(offset
< bufLen
);
222 reinterpret_cast<PSYSTEM_PROCESS_INFORMATION
>(buf
.get() + offset
);
223 ULONG pid
= HandleToUlong(processInfo
->UniqueProcessId
);
224 // Check if we are interested in this process.
225 auto processLookup
= gathered
.lookup(pid
);
227 for (ULONG i
= 0; i
< processInfo
->NumberOfThreads
; ++i
) {
228 // The thread information structs are stored in the buffer right
229 // after the SYSTEM_PROCESS_INFORMATION struct.
230 PSYSTEM_THREAD_INFORMATION thread
=
231 reinterpret_cast<PSYSTEM_THREAD_INFORMATION
>(
232 buf
.get() + offset
+ sizeof(SYSTEM_PROCESS_INFORMATION
) +
233 sizeof(SYSTEM_THREAD_INFORMATION
) * i
);
234 ULONG tid
= HandleToUlong(thread
->ClientId
.UniqueThread
);
236 ThreadInfo
* threadInfo
=
237 processLookup
->value().threads
.AppendElement(fallible
);
239 result
.SetReject(NS_ERROR_OUT_OF_MEMORY
);
243 nsAutoHandle
hThread(
244 OpenThread(/* dwDesiredAccess = */ THREAD_QUERY_INFORMATION
,
245 /* bInheritHandle = */ FALSE
,
246 /* dwThreadId = */ tid
));
248 // Cannot open thread. Not sure why, but let's erase this thread
249 // and attempt to find data on other threads.
250 processLookup
->value().threads
.RemoveLastElement();
254 threadInfo
->tid
= tid
;
256 // Attempt to get thread times.
257 // If we fail, continue without this piece of information.
258 if (QueryThreadCycleTime(hThread
.get(), &threadInfo
->cpuCycleCount
) &&
260 threadInfo
->cpuTime
=
261 threadInfo
->cpuCycleCount
* PR_USEC_PER_NSEC
/ frequencyInMHz
;
263 FILETIME createTime
, exitTime
, kernelTime
, userTime
;
264 if (GetThreadTimes(hThread
.get(), &createTime
, &exitTime
, &kernelTime
,
266 threadInfo
->cpuTime
=
267 ToNanoSeconds(kernelTime
) + ToNanoSeconds(userTime
);
271 // Attempt to get thread name.
272 // If we fail, continue without this piece of information.
273 if (getThreadDescription
) {
274 PWSTR threadName
= nullptr;
275 if (getThreadDescription(hThread
.get(), &threadName
) && threadName
) {
276 threadInfo
->name
= threadName
;
279 LocalFree(threadName
);
285 if (processInfo
->NextEntryOffset
== 0) {
290 // ----- We're ready to return.
291 result
.SetResolve(std::move(gathered
));
295 } // namespace mozilla