Bug 1742522 - Synchronous version of GetProcInfo - r=florian
Differential Revision: https://phabricator.services.mozilla.com/D132212
This commit is contained in:
@@ -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`.
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user