Bug 1942239 - Add option to explicitly enable incremental origin initialization in...
[gecko.git] / toolkit / components / processtools / ProcInfo_win.cpp
blob02b63190ed358524b6b30303e3f8eb42f1f63903
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"
13 #include <windows.h>
14 #include <psapi.h>
15 #include <winternl.h>
16 #include <xpcpublic.h>
18 #ifndef STATUS_INFO_LENGTH_MISMATCH
19 # define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
20 #endif
22 #define PR_USEC_PER_NSEC 1000L
24 typedef HRESULT(WINAPI* GETTHREADDESCRIPTION)(HANDLE hThread,
25 PWSTR* threadDescription);
27 namespace mozilla {
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.
38 HKEY key;
39 static const WCHAR keyName[] =
40 L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0";
42 if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, keyName, 0, KEY_QUERY_VALUE, &key) ==
43 ERROR_SUCCESS) {
44 DWORD data, len;
45 len = sizeof(data);
47 if (RegQueryValueEx(key, L"~Mhz", 0, 0, reinterpret_cast<LPBYTE>(&data),
48 &len) == ERROR_SUCCESS) {
49 return static_cast<int>(data);
53 return 0;
54 }();
56 return frequency;
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
64 // times < 1/64s.
65 if (!mozilla::has_constant_tsc() && !xpc::IsInAutomation()) {
66 return 0;
69 return GetCpuFrequencyMHz();
70 }();
72 return frequency;
75 nsresult GetCpuTimeSinceProcessStartInMs(uint64_t* aResult) {
76 int frequencyInMHz = GetCycleTimeFrequencyMHz();
77 if (frequencyInMHz) {
78 uint64_t cpuCycleCount;
79 if (!QueryProcessCycleTime(::GetCurrentProcess(), &cpuCycleCount)) {
80 return NS_ERROR_FAILURE;
82 constexpr int HZ_PER_MHZ = 1000000;
83 *aResult =
84 cpuCycleCount / (frequencyInMHz * (HZ_PER_MHZ / PR_MSEC_PER_SEC));
85 return NS_OK;
88 FILETIME createTime, exitTime, kernelTime, userTime;
89 if (!GetProcessTimes(::GetCurrentProcess(), &createTime, &exitTime,
90 &kernelTime, &userTime)) {
91 return NS_ERROR_FAILURE;
93 *aResult =
94 (ToNanoSeconds(kernelTime) + ToNanoSeconds(userTime)) / PR_NSEC_PER_MSEC;
95 return NS_OK;
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);
109 return result;
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));
120 if (!handle) {
121 // Ignore process, it may have died.
122 continue;
125 uint64_t cpuCycleTime;
126 if (!QueryProcessCycleTime(handle.get(), &cpuCycleTime)) {
127 // Ignore process, it may have died.
128 continue;
131 uint64_t cpuTime;
132 if (frequencyInMHz) {
133 cpuTime = cpuCycleTime * PR_USEC_PER_NSEC / frequencyInMHz;
134 } else {
135 FILETIME createTime, exitTime, kernelTime, userTime;
136 if (!GetProcessTimes(handle.get(), &createTime, &exitTime, &kernelTime,
137 &userTime)) {
138 // Ignore process, it may have died.
139 continue;
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.
149 continue;
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.
158 ProcInfo info;
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);
171 return result;
175 // ---- Add thread data to already-copied processes.
177 NTSTATUS ntStatus;
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.
185 do {
186 // These allocations can be hundreds of megabytes on some computers, so
187 // we should use fallible new here.
188 buf = MakeUniqueFallible<char[]>(bufLen);
189 if (!buf) {
190 result.SetReject(NS_ERROR_OUT_OF_MEMORY);
191 return result;
194 ntStatus = ::NtQuerySystemInformation(SystemProcessInformation, buf.get(),
195 bufLen, &bufLen);
196 if (ntStatus != STATUS_INFO_LENGTH_MISMATCH) {
197 break;
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;
205 } while (true);
206 if (!NT_SUCCESS(ntStatus)) {
207 result.SetReject(NS_ERROR_UNEXPECTED);
208 return result;
211 // `GetThreadDescription` is available as of Windows 10.
212 // We attempt to import it dynamically, knowing that it
213 // may be `nullptr`.
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);
221 processInfo =
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);
226 if (processLookup) {
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);
238 if (!threadInfo) {
239 result.SetReject(NS_ERROR_OUT_OF_MEMORY);
240 return result;
243 nsAutoHandle hThread(
244 OpenThread(/* dwDesiredAccess = */ THREAD_QUERY_INFORMATION,
245 /* bInheritHandle = */ FALSE,
246 /* dwThreadId = */ tid));
247 if (!hThread) {
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();
251 continue;
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) &&
259 frequencyInMHz) {
260 threadInfo->cpuTime =
261 threadInfo->cpuCycleCount * PR_USEC_PER_NSEC / frequencyInMHz;
262 } else {
263 FILETIME createTime, exitTime, kernelTime, userTime;
264 if (GetThreadTimes(hThread.get(), &createTime, &exitTime, &kernelTime,
265 &userTime)) {
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;
278 if (threadName) {
279 LocalFree(threadName);
285 if (processInfo->NextEntryOffset == 0) {
286 break;
290 // ----- We're ready to return.
291 result.SetResolve(std::move(gathered));
292 return result;
295 } // namespace mozilla