Bug 1742522 - Synchronous version of GetProcInfo - r=florian

Differential Revision: https://phabricator.services.mozilla.com/D132212
This commit is contained in:
Gerald Squelart
2021-12-02 08:52:39 +00:00
parent cbf18c8433
commit a2381e0556
4 changed files with 369 additions and 341 deletions

View File

@@ -215,6 +215,12 @@ struct ProcInfoRequest {
*/
RefPtr<ProcInfoPromise> GetProcInfo(nsTArray<ProcInfoRequest>&& aRequests);
/**
* Synchronous version of GetProcInfo.
*/
ProcInfoPromise::ResolveOrRejectValue GetProcInfoSync(
nsTArray<ProcInfoRequest>&& aRequests);
/**
* Utility function: copy data from a `ProcInfo` and into either a
* `ParentProcInfoDictionary` or a `ChildProcInfoDictionary`.

View File

@@ -44,6 +44,119 @@ nsresult GetGpuTimeSinceProcessStartInMs(uint64_t* aResult) {
return NS_OK;
}
ProcInfoPromise::ResolveOrRejectValue GetProcInfoSync(nsTArray<ProcInfoRequest>&& aRequests) {
ProcInfoPromise::ResolveOrRejectValue result;
HashMap<base::ProcessId, ProcInfo> gathered;
if (!gathered.reserve(aRequests.Length())) {
result.SetReject(NS_ERROR_OUT_OF_MEMORY);
return result;
}
for (const auto& request : aRequests) {
ProcInfo info;
info.pid = request.pid;
info.childId = request.childId;
info.type = request.processType;
info.origin = std::move(request.origin);
info.windows = std::move(request.windowInfo);
struct proc_taskinfo pti;
if ((unsigned long)proc_pidinfo(request.pid, PROC_PIDTASKINFO, 0, &pti, PROC_PIDTASKINFO_SIZE) <
PROC_PIDTASKINFO_SIZE) {
// Can't read data for this process.
// Probably either a sandboxing issue or a race condition, e.g.
// the process has been just been killed. Regardless, skip process.
continue;
}
info.cpuTime = pti.pti_total_user + pti.pti_total_system;
mach_port_t selectedTask;
// If we did not get a task from a child process, we use mach_task_self()
if (request.childTask == MACH_PORT_NULL) {
selectedTask = mach_task_self();
} else {
selectedTask = request.childTask;
}
// The phys_footprint value (introduced in 10.11) of the TASK_VM_INFO data
// matches the value in the 'Memory' column of the Activity Monitor.
task_vm_info_data_t task_vm_info;
mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
kern_return_t kr = task_info(selectedTask, TASK_VM_INFO, (task_info_t)&task_vm_info, &count);
info.memory = kr == KERN_SUCCESS ? task_vm_info.phys_footprint : 0;
// Now getting threads info
// task_threads() gives us a snapshot of the process threads
// but those threads can go away. All the code below makes
// the assumption that thread_info() calls may fail, and
// these errors will be ignored.
thread_act_port_array_t threadList;
mach_msg_type_number_t threadCount;
kern_return_t kret = task_threads(selectedTask, &threadList, &threadCount);
if (kret != KERN_SUCCESS) {
// For some reason, we have no data on the threads for this process.
// Most likely reason is that we have just lost a race condition and
// the process is dead.
// Let's stop here and ignore the entire process.
continue;
}
// Deallocate the thread list.
// Note that this deallocation is entirely undocumented, so the following code is based
// on guesswork and random examples found on the web.
auto guardThreadCount = MakeScopeExit([&] {
if (threadList == nullptr) {
return;
}
// Free each thread to avoid leaks.
for (mach_msg_type_number_t i = 0; i < threadCount; i++) {
mach_port_deallocate(mach_task_self(), threadList[i]);
}
vm_deallocate(mach_task_self(), /* address */ (vm_address_t)threadList,
/* size */ sizeof(thread_t) * threadCount);
});
for (mach_msg_type_number_t i = 0; i < threadCount; i++) {
// Basic thread info.
thread_extended_info_data_t threadInfoData;
count = THREAD_EXTENDED_INFO_COUNT;
kret =
thread_info(threadList[i], THREAD_EXTENDED_INFO, (thread_info_t)&threadInfoData, &count);
if (kret != KERN_SUCCESS) {
continue;
}
// Getting the thread id.
thread_identifier_info identifierInfo;
count = THREAD_IDENTIFIER_INFO_COUNT;
kret = thread_info(threadList[i], THREAD_IDENTIFIER_INFO, (thread_info_t)&identifierInfo,
&count);
if (kret != KERN_SUCCESS) {
continue;
}
// The two system calls were successful, let's add that thread
ThreadInfo* thread = info.threads.AppendElement(fallible);
if (!thread) {
result.SetReject(NS_ERROR_OUT_OF_MEMORY);
return result;
}
thread->cpuTime = threadInfoData.pth_user_time + threadInfoData.pth_system_time;
thread->name.AssignASCII(threadInfoData.pth_name);
thread->tid = identifierInfo.thread_id;
}
if (!gathered.put(request.pid, std::move(info))) {
result.SetReject(NS_ERROR_OUT_OF_MEMORY);
return result;
}
}
result.SetResolve(std::move(gathered));
return result;
}
RefPtr<ProcInfoPromise> GetProcInfo(nsTArray<ProcInfoRequest>&& aRequests) {
auto holder = MakeUnique<MozPromiseHolder<ProcInfoPromise>>();
RefPtr<ProcInfoPromise> promise = holder->Ensure(__func__);
@@ -55,117 +168,11 @@ RefPtr<ProcInfoPromise> GetProcInfo(nsTArray<ProcInfoRequest>&& aRequests) {
return promise;
}
RefPtr<nsIRunnable> r = NS_NewRunnableFunction(__func__, [holder = std::move(holder),
requests = std::move(aRequests)]() {
HashMap<base::ProcessId, ProcInfo> gathered;
if (!gathered.reserve(requests.Length())) {
holder->Reject(NS_ERROR_OUT_OF_MEMORY, __func__);
return;
}
for (const auto& request : requests) {
ProcInfo info;
info.pid = request.pid;
info.childId = request.childId;
info.type = request.processType;
info.origin = std::move(request.origin);
info.windows = std::move(request.windowInfo);
struct proc_taskinfo pti;
if ((unsigned long)proc_pidinfo(request.pid, PROC_PIDTASKINFO, 0, &pti,
PROC_PIDTASKINFO_SIZE) < PROC_PIDTASKINFO_SIZE) {
// Can't read data for this process.
// Probably either a sandboxing issue or a race condition, e.g.
// the process has been just been killed. Regardless, skip process.
continue;
}
info.cpuTime = pti.pti_total_user + pti.pti_total_system;
mach_port_t selectedTask;
// If we did not get a task from a child process, we use mach_task_self()
if (request.childTask == MACH_PORT_NULL) {
selectedTask = mach_task_self();
} else {
selectedTask = request.childTask;
}
// The phys_footprint value (introduced in 10.11) of the TASK_VM_INFO data
// matches the value in the 'Memory' column of the Activity Monitor.
task_vm_info_data_t task_vm_info;
mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
kern_return_t kr = task_info(selectedTask, TASK_VM_INFO, (task_info_t)&task_vm_info, &count);
info.memory = kr == KERN_SUCCESS ? task_vm_info.phys_footprint : 0;
// Now getting threads info
// task_threads() gives us a snapshot of the process threads
// but those threads can go away. All the code below makes
// the assumption that thread_info() calls may fail, and
// these errors will be ignored.
thread_act_port_array_t threadList;
mach_msg_type_number_t threadCount;
kern_return_t kret = task_threads(selectedTask, &threadList, &threadCount);
if (kret != KERN_SUCCESS) {
// For some reason, we have no data on the threads for this process.
// Most likely reason is that we have just lost a race condition and
// the process is dead.
// Let's stop here and ignore the entire process.
continue;
}
// Deallocate the thread list.
// Note that this deallocation is entirely undocumented, so the following code is based
// on guesswork and random examples found on the web.
auto guardThreadCount = MakeScopeExit([&] {
if (threadList == nullptr) {
return;
}
// Free each thread to avoid leaks.
for (mach_msg_type_number_t i = 0; i < threadCount; i++) {
mach_port_deallocate(mach_task_self(), threadList[i]);
}
vm_deallocate(mach_task_self(), /* address */ (vm_address_t)threadList,
/* size */ sizeof(thread_t) * threadCount);
RefPtr<nsIRunnable> r = NS_NewRunnableFunction(
__func__, [holder = std::move(holder), requests = std::move(aRequests)]() mutable {
holder->ResolveOrReject(GetProcInfoSync(std::move(requests)), __func__);
});
for (mach_msg_type_number_t i = 0; i < threadCount; i++) {
// Basic thread info.
thread_extended_info_data_t threadInfoData;
count = THREAD_EXTENDED_INFO_COUNT;
kret = thread_info(threadList[i], THREAD_EXTENDED_INFO, (thread_info_t)&threadInfoData,
&count);
if (kret != KERN_SUCCESS) {
continue;
}
// Getting the thread id.
thread_identifier_info identifierInfo;
count = THREAD_IDENTIFIER_INFO_COUNT;
kret = thread_info(threadList[i], THREAD_IDENTIFIER_INFO, (thread_info_t)&identifierInfo,
&count);
if (kret != KERN_SUCCESS) {
continue;
}
// The two system calls were successful, let's add that thread
ThreadInfo* thread = info.threads.AppendElement(fallible);
if (!thread) {
holder->Reject(NS_ERROR_OUT_OF_MEMORY, __func__);
return;
}
thread->cpuTime = threadInfoData.pth_user_time + threadInfoData.pth_system_time;
thread->name.AssignASCII(threadInfoData.pth_name);
thread->tid = identifierInfo.thread_id;
}
if (!gathered.put(request.pid, std::move(info))) {
holder->Reject(NS_ERROR_OUT_OF_MEMORY, __func__);
return;
}
}
// ... and we're done!
holder->Resolve(std::move(gathered), __func__);
});
rv = target->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
if (NS_FAILED(rv)) {
NS_WARNING("Failed to dispatch the LoadDataRunnable.");

View File

@@ -200,6 +200,94 @@ nsresult GetGpuTimeSinceProcessStartInMs(uint64_t* aResult) {
return NS_ERROR_NOT_IMPLEMENTED;
}
ProcInfoPromise::ResolveOrRejectValue GetProcInfoSync(
nsTArray<ProcInfoRequest>&& aRequests) {
ProcInfoPromise::ResolveOrRejectValue result;
HashMap<base::ProcessId, ProcInfo> gathered;
if (!gathered.reserve(aRequests.Length())) {
result.SetReject(NS_ERROR_OUT_OF_MEMORY);
return result;
}
for (const auto& request : aRequests) {
// opening the stat file and reading its content
StatReader reader(request.pid);
ProcInfo info;
nsresult rv = reader.ParseProc(info);
if (NS_FAILED(rv)) {
// Can't read data for this proc.
// Probably either a sandboxing issue or a race condition, e.g.
// the process has been just been killed. Regardless, skip process.
continue;
}
// The 'Memory' value displayed in the system monitor is resident -
// shared. statm contains more fields, but we're only interested in
// the first three.
static const int MAX_FIELD = 3;
size_t VmSize, resident, shared;
info.memory = 0;
FILE* f = fopen(nsPrintfCString("/proc/%u/statm", request.pid).get(), "r");
if (f) {
int nread = fscanf(f, "%zu %zu %zu", &VmSize, &resident, &shared);
fclose(f);
if (nread == MAX_FIELD) {
info.memory = (resident - shared) * getpagesize();
}
}
// Extra info
info.pid = request.pid;
info.childId = request.childId;
info.type = request.processType;
info.origin = request.origin;
info.windows = std::move(request.windowInfo);
// Let's look at the threads
nsCString taskPath;
taskPath.AppendPrintf("/proc/%u/task", request.pid);
DIR* dirHandle = opendir(taskPath.get());
if (!dirHandle) {
// For some reason, we have no data on the threads for this process.
// Most likely reason is that we have just lost a race condition and
// the process is dead.
// Let's stop here and ignore the entire process.
continue;
}
auto cleanup = mozilla::MakeScopeExit([&] { closedir(dirHandle); });
// If we can't read some thread info, we ignore that thread.
dirent* entry;
while ((entry = readdir(dirHandle)) != nullptr) {
if (entry->d_name[0] == '.') {
continue;
}
// Threads have a stat file, like processes.
nsAutoCString entryName(entry->d_name);
int32_t tid = entryName.ToInteger(&rv);
if (NS_FAILED(rv)) {
continue;
}
ThreadInfoReader reader(request.pid, tid);
ThreadInfo threadInfo;
rv = reader.ParseThread(threadInfo);
if (NS_FAILED(rv)) {
continue;
}
info.threads.AppendElement(threadInfo);
}
if (!gathered.put(request.pid, std::move(info))) {
result.SetReject(NS_ERROR_OUT_OF_MEMORY);
return result;
}
}
// ... and we're done!
result.SetResolve(std::move(gathered));
return result;
}
RefPtr<ProcInfoPromise> GetProcInfo(nsTArray<ProcInfoRequest>&& aRequests) {
auto holder = MakeUnique<MozPromiseHolder<ProcInfoPromise>>();
RefPtr<ProcInfoPromise> promise = holder->Ensure(__func__);
@@ -214,89 +302,8 @@ RefPtr<ProcInfoPromise> GetProcInfo(nsTArray<ProcInfoRequest>&& aRequests) {
RefPtr<nsIRunnable> r = NS_NewRunnableFunction(
__func__,
[holder = std::move(holder), requests = std::move(aRequests)]() {
HashMap<base::ProcessId, ProcInfo> gathered;
if (!gathered.reserve(requests.Length())) {
holder->Reject(NS_ERROR_OUT_OF_MEMORY, __func__);
return;
}
for (const auto& request : requests) {
// opening the stat file and reading its content
StatReader reader(request.pid);
ProcInfo info;
nsresult rv = reader.ParseProc(info);
if (NS_FAILED(rv)) {
// Can't read data for this proc.
// Probably either a sandboxing issue or a race condition, e.g.
// the process has been just been killed. Regardless, skip process.
continue;
}
// The 'Memory' value displayed in the system monitor is resident -
// shared. statm contains more fields, but we're only interested in
// the first three.
static const int MAX_FIELD = 3;
size_t VmSize, resident, shared;
info.memory = 0;
FILE* f =
fopen(nsPrintfCString("/proc/%u/statm", request.pid).get(), "r");
if (f) {
int nread = fscanf(f, "%zu %zu %zu", &VmSize, &resident, &shared);
fclose(f);
if (nread == MAX_FIELD) {
info.memory = (resident - shared) * getpagesize();
}
}
// Extra info
info.pid = request.pid;
info.childId = request.childId;
info.type = request.processType;
info.origin = request.origin;
info.windows = std::move(request.windowInfo);
// Let's look at the threads
nsCString taskPath;
taskPath.AppendPrintf("/proc/%u/task", request.pid);
DIR* dirHandle = opendir(taskPath.get());
if (!dirHandle) {
// For some reason, we have no data on the threads for this process.
// Most likely reason is that we have just lost a race condition and
// the process is dead.
// Let's stop here and ignore the entire process.
continue;
}
auto cleanup = mozilla::MakeScopeExit([&] { closedir(dirHandle); });
// If we can't read some thread info, we ignore that thread.
dirent* entry;
while ((entry = readdir(dirHandle)) != nullptr) {
if (entry->d_name[0] == '.') {
continue;
}
// Threads have a stat file, like processes.
nsAutoCString entryName(entry->d_name);
int32_t tid = entryName.ToInteger(&rv);
if (NS_FAILED(rv)) {
continue;
}
ThreadInfoReader reader(request.pid, tid);
ThreadInfo threadInfo;
rv = reader.ParseThread(threadInfo);
if (NS_FAILED(rv)) {
continue;
}
info.threads.AppendElement(threadInfo);
}
if (!gathered.put(request.pid, std::move(info))) {
holder->Reject(NS_ERROR_OUT_OF_MEMORY, __func__);
return;
}
}
// ... and we're done!
holder->Resolve(std::move(gathered), __func__);
[holder = std::move(holder), requests = std::move(aRequests)]() mutable {
holder->ResolveOrReject(GetProcInfoSync(std::move(requests)), __func__);
});
rv = target->Dispatch(r.forget(), NS_DISPATCH_NORMAL);

View File

@@ -90,6 +90,160 @@ nsresult GetGpuTimeSinceProcessStartInMs(uint64_t* aResult) {
return gfxWindowsPlatform::GetGpuTimeSinceProcessStartInMs(aResult);
}
ProcInfoPromise::ResolveOrRejectValue GetProcInfoSync(
nsTArray<ProcInfoRequest>&& aRequests) {
ProcInfoPromise::ResolveOrRejectValue result;
HashMap<base::ProcessId, ProcInfo> gathered;
if (!gathered.reserve(aRequests.Length())) {
result.SetReject(NS_ERROR_OUT_OF_MEMORY);
return result;
}
int frequencyInMHz = GetCycleTimeFrequency();
// ---- Copying data on processes (minus threads).
for (const auto& request : aRequests) {
nsAutoHandle handle(OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
FALSE, request.pid));
if (!handle) {
// Ignore process, it may have died.
continue;
}
uint64_t cpuCycleTime;
if (!QueryProcessCycleTime(handle.get(), &cpuCycleTime)) {
// Ignore process, it may have died.
continue;
}
uint64_t cpuTime;
if (frequencyInMHz) {
cpuTime = cpuCycleTime * PR_USEC_PER_NSEC / frequencyInMHz;
} else {
FILETIME createTime, exitTime, kernelTime, userTime;
if (!GetProcessTimes(handle.get(), &createTime, &exitTime, &kernelTime,
&userTime)) {
// Ignore process, it may have died.
continue;
}
cpuTime = ToNanoSeconds(kernelTime) + ToNanoSeconds(userTime);
}
PROCESS_MEMORY_COUNTERS_EX memoryCounters;
if (!GetProcessMemoryInfo(handle.get(),
(PPROCESS_MEMORY_COUNTERS)&memoryCounters,
sizeof(memoryCounters))) {
// Ignore process, it may have died.
continue;
}
// Assumption: values of `pid` are distinct between processes,
// regardless of any race condition we might have stumbled upon. Even
// if it somehow could happen, in the worst case scenario, we might
// end up overwriting one process info and we might end up with too
// many threads attached to a process, as the data is not crucial, we
// do not need to defend against that (unlikely) scenario.
ProcInfo info;
info.pid = request.pid;
info.childId = request.childId;
info.type = request.processType;
info.origin = request.origin;
info.windows = std::move(request.windowInfo);
info.cpuTime = cpuTime;
info.cpuCycleCount = cpuCycleTime;
info.memory = memoryCounters.PrivateUsage;
if (!gathered.put(request.pid, std::move(info))) {
result.SetReject(NS_ERROR_OUT_OF_MEMORY);
return result;
}
}
// ---- Add thread data to already-copied processes.
// First, we need to capture a snapshot of all the threads on this
// system.
nsAutoHandle hThreadSnap(CreateToolhelp32Snapshot(
/* dwFlags */ TH32CS_SNAPTHREAD, /* ignored */ 0));
if (!hThreadSnap) {
result.SetReject(NS_ERROR_UNEXPECTED);
return result;
}
// `GetThreadDescription` is available as of Windows 10.
// We attempt to import it dynamically, knowing that it
// may be `nullptr`.
auto getThreadDescription =
reinterpret_cast<GETTHREADDESCRIPTION>(::GetProcAddress(
::GetModuleHandleW(L"Kernel32.dll"), "GetThreadDescription"));
THREADENTRY32 te32;
te32.dwSize = sizeof(THREADENTRY32);
// Now, walk through the threads.
for (auto success = Thread32First(hThreadSnap.get(), &te32); success;
success = Thread32Next(hThreadSnap.get(), &te32)) {
auto processLookup = gathered.lookup(te32.th32OwnerProcessID);
if (!processLookup) {
// Not one of the processes we're interested in.
continue;
}
ThreadInfo* threadInfo =
processLookup->value().threads.AppendElement(fallible);
if (!threadInfo) {
result.SetReject(NS_ERROR_OUT_OF_MEMORY);
return result;
}
nsAutoHandle hThread(
OpenThread(/* dwDesiredAccess = */ THREAD_QUERY_INFORMATION,
/* bInheritHandle = */ FALSE,
/* dwThreadId = */ te32.th32ThreadID));
if (!hThread) {
// Cannot open thread. Not sure why, but let's erase this thread
// and attempt to find data on other threads.
processLookup->value().threads.RemoveLastElement();
continue;
}
threadInfo->tid = te32.th32ThreadID;
// Attempt to get thread times.
// If we fail, continue without this piece of information.
if (QueryThreadCycleTime(hThread.get(), &threadInfo->cpuCycleCount) &&
frequencyInMHz) {
threadInfo->cpuTime =
threadInfo->cpuCycleCount * PR_USEC_PER_NSEC / frequencyInMHz;
} else {
FILETIME createTime, exitTime, kernelTime, userTime;
if (GetThreadTimes(hThread.get(), &createTime, &exitTime, &kernelTime,
&userTime)) {
threadInfo->cpuTime =
ToNanoSeconds(kernelTime) + ToNanoSeconds(userTime);
}
}
// Attempt to get thread name.
// If we fail, continue without this piece of information.
if (getThreadDescription) {
PWSTR threadName = nullptr;
if (getThreadDescription(hThread.get(), &threadName) && threadName) {
threadInfo->name = threadName;
}
if (threadName) {
LocalFree(threadName);
}
}
}
// ----- We're ready to return.
result.SetResolve(std::move(gathered));
return result;
}
RefPtr<ProcInfoPromise> GetProcInfo(nsTArray<ProcInfoRequest>&& aRequests) {
auto holder = MakeUnique<MozPromiseHolder<ProcInfoPromise>>();
RefPtr<ProcInfoPromise> promise = holder->Ensure(__func__);
@@ -105,155 +259,9 @@ RefPtr<ProcInfoPromise> GetProcInfo(nsTArray<ProcInfoRequest>&& aRequests) {
RefPtr<nsIRunnable> r = NS_NewRunnableFunction(
__func__,
[holder = std::move(holder), requests = std::move(aRequests)]() -> void {
HashMap<base::ProcessId, ProcInfo> gathered;
if (!gathered.reserve(requests.Length())) {
holder->Reject(NS_ERROR_OUT_OF_MEMORY, __func__);
return;
}
int frequencyInMHz = GetCycleTimeFrequency();
// ---- Copying data on processes (minus threads).
for (const auto& request : requests) {
nsAutoHandle handle(OpenProcess(
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, request.pid));
if (!handle) {
// Ignore process, it may have died.
continue;
}
uint64_t cpuCycleTime;
if (!QueryProcessCycleTime(handle.get(), &cpuCycleTime)) {
// Ignore process, it may have died.
continue;
}
uint64_t cpuTime;
if (frequencyInMHz) {
cpuTime = cpuCycleTime * PR_USEC_PER_NSEC / frequencyInMHz;
} else {
FILETIME createTime, exitTime, kernelTime, userTime;
if (!GetProcessTimes(handle.get(), &createTime, &exitTime,
&kernelTime, &userTime)) {
// Ignore process, it may have died.
continue;
}
cpuTime = ToNanoSeconds(kernelTime) + ToNanoSeconds(userTime);
}
PROCESS_MEMORY_COUNTERS_EX memoryCounters;
if (!GetProcessMemoryInfo(handle.get(),
(PPROCESS_MEMORY_COUNTERS)&memoryCounters,
sizeof(memoryCounters))) {
// Ignore process, it may have died.
continue;
}
// Assumption: values of `pid` are distinct between processes,
// regardless of any race condition we might have stumbled upon. Even
// if it somehow could happen, in the worst case scenario, we might
// end up overwriting one process info and we might end up with too
// many threads attached to a process, as the data is not crucial, we
// do not need to defend against that (unlikely) scenario.
ProcInfo info;
info.pid = request.pid;
info.childId = request.childId;
info.type = request.processType;
info.origin = request.origin;
info.windows = std::move(request.windowInfo);
info.cpuTime = cpuTime;
info.cpuCycleCount = cpuCycleTime;
info.memory = memoryCounters.PrivateUsage;
if (!gathered.put(request.pid, std::move(info))) {
holder->Reject(NS_ERROR_OUT_OF_MEMORY, __func__);
return;
}
}
// ---- Add thread data to already-copied processes.
// First, we need to capture a snapshot of all the threads on this
// system.
nsAutoHandle hThreadSnap(CreateToolhelp32Snapshot(
/* dwFlags */ TH32CS_SNAPTHREAD, /* ignored */ 0));
if (!hThreadSnap) {
holder->Reject(NS_ERROR_UNEXPECTED, __func__);
return;
}
// `GetThreadDescription` is available as of Windows 10.
// We attempt to import it dynamically, knowing that it
// may be `nullptr`.
auto getThreadDescription =
reinterpret_cast<GETTHREADDESCRIPTION>(::GetProcAddress(
::GetModuleHandleW(L"Kernel32.dll"), "GetThreadDescription"));
THREADENTRY32 te32;
te32.dwSize = sizeof(THREADENTRY32);
// Now, walk through the threads.
for (auto success = Thread32First(hThreadSnap.get(), &te32); success;
success = Thread32Next(hThreadSnap.get(), &te32)) {
auto processLookup = gathered.lookup(te32.th32OwnerProcessID);
if (!processLookup) {
// Not one of the processes we're interested in.
continue;
}
ThreadInfo* threadInfo =
processLookup->value().threads.AppendElement(fallible);
if (!threadInfo) {
holder->Reject(NS_ERROR_OUT_OF_MEMORY, __func__);
return;
}
nsAutoHandle hThread(
OpenThread(/* dwDesiredAccess = */ THREAD_QUERY_INFORMATION,
/* bInheritHandle = */ FALSE,
/* dwThreadId = */ te32.th32ThreadID));
if (!hThread) {
// Cannot open thread. Not sure why, but let's erase this thread
// and attempt to find data on other threads.
processLookup->value().threads.RemoveLastElement();
continue;
}
threadInfo->tid = te32.th32ThreadID;
// Attempt to get thread times.
// If we fail, continue without this piece of information.
if (QueryThreadCycleTime(hThread.get(), &threadInfo->cpuCycleCount) &&
frequencyInMHz) {
threadInfo->cpuTime =
threadInfo->cpuCycleCount * PR_USEC_PER_NSEC / frequencyInMHz;
} else {
FILETIME createTime, exitTime, kernelTime, userTime;
if (GetThreadTimes(hThread.get(), &createTime, &exitTime,
&kernelTime, &userTime)) {
threadInfo->cpuTime =
ToNanoSeconds(kernelTime) + ToNanoSeconds(userTime);
}
}
// Attempt to get thread name.
// If we fail, continue without this piece of information.
if (getThreadDescription) {
PWSTR threadName = nullptr;
if (getThreadDescription(hThread.get(), &threadName) &&
threadName) {
threadInfo->name = threadName;
}
if (threadName) {
LocalFree(threadName);
}
}
}
// ----- We're ready to return.
holder->Resolve(std::move(gathered), __func__);
[holder = std::move(holder),
requests = std::move(aRequests)]() mutable -> void {
holder->ResolveOrReject(GetProcInfoSync(std::move(requests)), __func__);
});
rv = target->Dispatch(r.forget(), NS_DISPATCH_NORMAL);