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 "nsMemoryReporterManager.h"
14 typedef HRESULT(WINAPI
* GETTHREADDESCRIPTION
)(HANDLE hThread
,
15 PWSTR
* threadDescription
);
19 uint64_t ToNanoSeconds(const FILETIME
& aFileTime
) {
20 // FILETIME values are 100-nanoseconds units, converting
21 ULARGE_INTEGER usec
= {{aFileTime
.dwLowDateTime
, aFileTime
.dwHighDateTime
}};
22 return usec
.QuadPart
* 100;
25 RefPtr
<ProcInfoPromise
> GetProcInfo(nsTArray
<ProcInfoRequest
>&& aRequests
) {
26 auto holder
= MakeUnique
<MozPromiseHolder
<ProcInfoPromise
>>();
27 RefPtr
<ProcInfoPromise
> promise
= holder
->Ensure(__func__
);
30 nsCOMPtr
<nsIEventTarget
> target
=
31 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID
, &rv
);
33 NS_WARNING("Failed to get stream transport service");
34 holder
->Reject(rv
, __func__
);
38 RefPtr
<nsIRunnable
> r
= NS_NewRunnableFunction(
40 [holder
= std::move(holder
), requests
= std::move(aRequests
)]() -> void {
41 HashMap
<base::ProcessId
, ProcInfo
> gathered
;
42 if (!gathered
.reserve(requests
.Length())) {
43 holder
->Reject(NS_ERROR_OUT_OF_MEMORY
, __func__
);
47 // ---- Copying data on processes (minus threads).
49 for (const auto& request
: requests
) {
50 nsAutoHandle
handle(OpenProcess(
51 PROCESS_QUERY_INFORMATION
| PROCESS_VM_READ
, FALSE
, request
.pid
));
54 // Ignore process, it may have died.
58 wchar_t filename
[MAX_PATH
];
59 if (GetProcessImageFileNameW(handle
.get(), filename
, MAX_PATH
) == 0) {
60 // Ignore process, it may have died.
63 FILETIME createTime
, exitTime
, kernelTime
, userTime
;
64 if (!GetProcessTimes(handle
.get(), &createTime
, &exitTime
,
65 &kernelTime
, &userTime
)) {
66 // Ignore process, it may have died.
69 PROCESS_MEMORY_COUNTERS memoryCounters
;
70 if (!GetProcessMemoryInfo(handle
.get(),
71 (PPROCESS_MEMORY_COUNTERS
)&memoryCounters
,
72 sizeof(memoryCounters
))) {
73 // Ignore process, it may have died.
77 // Assumption: values of `pid` are distinct between processes,
78 // regardless of any race condition we might have stumbled upon. Even
79 // if it somehow could happen, in the worst case scenario, we might
80 // end up overwriting one process info and we might end up with too
81 // many threads attached to a process, as the data is not crucial, we
82 // do not need to defend against that (unlikely) scenario.
84 info
.pid
= request
.pid
;
85 info
.childId
= request
.childId
;
86 info
.type
= request
.processType
;
87 info
.origin
= request
.origin
;
88 info
.windows
= std::move(request
.windowInfo
);
89 info
.filename
.Assign(filename
);
90 info
.cpuKernel
= ToNanoSeconds(kernelTime
);
91 info
.cpuUser
= ToNanoSeconds(userTime
);
92 info
.residentSetSize
= memoryCounters
.WorkingSetSize
;
94 // Computing the resident unique size is somewhat tricky,
95 // so we use about:memory's implementation. This implementation
96 // uses the `HANDLE` so, in theory, should be no additional
97 // race condition. However, in case of error, the result is `0`.
98 info
.residentUniqueSize
=
99 nsMemoryReporterManager::ResidentUnique(handle
.get());
101 if (!gathered
.put(request
.pid
, std::move(info
))) {
102 holder
->Reject(NS_ERROR_OUT_OF_MEMORY
, __func__
);
107 // ---- Add thread data to already-copied processes.
109 // First, we need to capture a snapshot of all the threads on this
111 nsAutoHandle
hThreadSnap(CreateToolhelp32Snapshot(
112 /* dwFlags */ TH32CS_SNAPTHREAD
, /* ignored */ 0));
114 holder
->Reject(NS_ERROR_UNEXPECTED
, __func__
);
118 // `GetThreadDescription` is available as of Windows 10.
119 // We attempt to import it dynamically, knowing that it
121 auto getThreadDescription
=
122 reinterpret_cast<GETTHREADDESCRIPTION
>(::GetProcAddress(
123 ::GetModuleHandleW(L
"Kernel32.dll"), "GetThreadDescription"));
126 te32
.dwSize
= sizeof(THREADENTRY32
);
128 // Now, walk through the threads.
129 for (auto success
= Thread32First(hThreadSnap
.get(), &te32
); success
;
130 success
= Thread32Next(hThreadSnap
.get(), &te32
)) {
131 auto processLookup
= gathered
.lookup(te32
.th32OwnerProcessID
);
132 if (!processLookup
) {
133 // Not one of the processes we're interested in.
136 ThreadInfo
* threadInfo
=
137 processLookup
->value().threads
.AppendElement(fallible
);
139 holder
->Reject(NS_ERROR_OUT_OF_MEMORY
, __func__
);
143 nsAutoHandle
hThread(
144 OpenThread(/* dwDesiredAccess = */ THREAD_QUERY_INFORMATION
,
145 /* bInheritHandle = */ FALSE
,
146 /* dwThreadId = */ te32
.th32ThreadID
));
148 // Cannot open thread. Not sure why, but let's erase this thread
149 // and attempt to find data on other threads.
150 processLookup
->value().threads
.RemoveLastElement();
154 threadInfo
->tid
= te32
.th32ThreadID
;
156 // Attempt to get thread times.
157 // If we fail, continue without this piece of information.
158 FILETIME createTime
, exitTime
, kernelTime
, userTime
;
159 if (GetThreadTimes(hThread
.get(), &createTime
, &exitTime
, &kernelTime
,
161 threadInfo
->cpuKernel
= ToNanoSeconds(kernelTime
);
162 threadInfo
->cpuUser
= ToNanoSeconds(userTime
);
165 // Attempt to get thread name.
166 // If we fail, continue without this piece of information.
167 if (getThreadDescription
) {
168 PWSTR threadName
= nullptr;
169 if (getThreadDescription(hThread
.get(), &threadName
) &&
171 threadInfo
->name
= threadName
;
174 LocalFree(threadName
);
179 // ----- We're ready to return.
180 holder
->Resolve(std::move(gathered
), __func__
);
183 rv
= target
->Dispatch(r
.forget(), NS_DISPATCH_NORMAL
);
185 NS_WARNING("Failed to dispatch the LoadDataRunnable.");
191 } // namespace mozilla