зеркало из https://github.com/mozilla/gecko-dev.git
Backed out 2 changesets (bug 1652000) for causing Bug 1653749.
Backed out changeset 69dbf38f4071 (bug 1652000) Backed out changeset 0a6cb920a247 (bug 1652000)
This commit is contained in:
Родитель
78c662789a
Коммит
c562fee6cd
|
@ -781,7 +781,7 @@ already_AddRefed<Promise> ChromeUtils::RequestProcInfo(GlobalObject& aGlobal,
|
|||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return nullptr;
|
||||
}
|
||||
// Prepare the JS promise that will hold our response.
|
||||
// Creating a JS promise
|
||||
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
|
||||
MOZ_ASSERT(global);
|
||||
RefPtr<Promise> domPromise = Promise::Create(global, aRv);
|
||||
|
@ -790,204 +790,213 @@ already_AddRefed<Promise> ChromeUtils::RequestProcInfo(GlobalObject& aGlobal,
|
|||
}
|
||||
MOZ_ASSERT(domPromise);
|
||||
|
||||
// Get a list of processes to examine and pre-fill them with available info.
|
||||
// Note that this is subject to race conditions: just because we have a
|
||||
// process in the list doesn't mean that the process will still be alive when
|
||||
// we attempt to get its information. Followup code MUST be able to fail
|
||||
// gracefully on some processes and still return whichever information is
|
||||
// available.
|
||||
|
||||
// Get all the content parents.
|
||||
// Note that this array includes even the long dead content parents, so we
|
||||
// might have some garbage, especially with Fission.
|
||||
// SAFETY NOTE: `contentParents` is only valid if used synchronously.
|
||||
// Anything else and you may end up dealing with dangling pointers.
|
||||
nsTArray<ContentParent*> contentParents;
|
||||
ContentParent::GetAll(contentParents);
|
||||
|
||||
// Prepare our background request.
|
||||
// We reserve one more slot for the browser process itself.
|
||||
nsTArray<ProcInfoRequest> requests(contentParents.Length() + 1);
|
||||
// Requesting process info for the browser process itself.
|
||||
requests.EmplaceBack(
|
||||
/* aPid = */ base::GetCurrentProcId(),
|
||||
/* aProcessType = */ ProcType::Browser,
|
||||
/* aOrigin = */ ""_ns);
|
||||
|
||||
mozilla::ipc::GeckoChildProcessHost::GetAll(
|
||||
[&requests,
|
||||
&contentParents](mozilla::ipc::GeckoChildProcessHost* aGeckoProcess) {
|
||||
auto handle = aGeckoProcess->GetChildProcessHandle();
|
||||
if (!handle) {
|
||||
// Something went wrong with this process, it may be dead already,
|
||||
// fail gracefully.
|
||||
return;
|
||||
}
|
||||
nsAutoCString origin;
|
||||
base::ProcessId childPid = base::GetProcId(handle);
|
||||
int32_t childId = 0;
|
||||
mozilla::ProcType type = mozilla::ProcType::Unknown;
|
||||
switch (aGeckoProcess->GetProcessType()) {
|
||||
case GeckoProcessType::GeckoProcessType_Content: {
|
||||
ContentParent* contentParent = nullptr;
|
||||
// This loop can become slow as we get more processes in
|
||||
// Fission, so might need some refactoring in the future.
|
||||
for (ContentParent* parent : contentParents) {
|
||||
// find the match
|
||||
if (parent->Process() == aGeckoProcess) {
|
||||
contentParent = parent;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!contentParent) {
|
||||
// FIXME: When can this happen?
|
||||
return;
|
||||
}
|
||||
// Converting the remoteType into a ProcType.
|
||||
// Ideally, the remoteType should be strongly typed
|
||||
// upstream, this would make the conversion less brittle.
|
||||
nsAutoCString remoteType(contentParent->GetRemoteType());
|
||||
if (StringBeginsWith(remoteType, FISSION_WEB_REMOTE_TYPE)) {
|
||||
// WARNING: Do not change the order, as
|
||||
// `DEFAULT_REMOTE_TYPE` is a prefix of
|
||||
// `FISSION_WEB_REMOTE_TYPE`.
|
||||
type = mozilla::ProcType::WebIsolated;
|
||||
} else if (StringBeginsWith(remoteType, DEFAULT_REMOTE_TYPE)) {
|
||||
type = mozilla::ProcType::Web;
|
||||
} else if (remoteType == FILE_REMOTE_TYPE) {
|
||||
type = mozilla::ProcType::File;
|
||||
} else if (remoteType == EXTENSION_REMOTE_TYPE) {
|
||||
type = mozilla::ProcType::Extension;
|
||||
} else if (remoteType == PRIVILEGEDABOUT_REMOTE_TYPE) {
|
||||
type = mozilla::ProcType::PrivilegedAbout;
|
||||
} else if (remoteType == PRIVILEGEDMOZILLA_REMOTE_TYPE) {
|
||||
type = mozilla::ProcType::PrivilegedMozilla;
|
||||
} else if (StringBeginsWith(remoteType,
|
||||
WITH_COOP_COEP_REMOTE_TYPE_PREFIX)) {
|
||||
type = mozilla::ProcType::WebCOOPCOEP;
|
||||
} else if (remoteType == LARGE_ALLOCATION_REMOTE_TYPE) {
|
||||
type = mozilla::ProcType::WebLargeAllocation;
|
||||
} else if (remoteType == PREALLOC_REMOTE_TYPE) {
|
||||
type = mozilla::ProcType::Preallocated;
|
||||
} else {
|
||||
MOZ_CRASH_UNSAFE_PRINTF("Unknown remoteType '%s'",
|
||||
remoteType.get());
|
||||
}
|
||||
|
||||
// By convention, everything after '=' is the origin.
|
||||
nsACString::const_iterator cursor;
|
||||
nsACString::const_iterator end;
|
||||
remoteType.BeginReading(cursor);
|
||||
remoteType.EndReading(end);
|
||||
if (FindCharInReadable('=', cursor, end)) {
|
||||
origin = Substring(++cursor, end);
|
||||
}
|
||||
childId = contentParent->ChildID();
|
||||
break;
|
||||
}
|
||||
case GeckoProcessType::GeckoProcessType_Default:
|
||||
type = mozilla::ProcType::Browser;
|
||||
break;
|
||||
case GeckoProcessType::GeckoProcessType_Plugin:
|
||||
type = mozilla::ProcType::Plugin;
|
||||
break;
|
||||
case GeckoProcessType::GeckoProcessType_GMPlugin:
|
||||
type = mozilla::ProcType::GMPlugin;
|
||||
break;
|
||||
case GeckoProcessType::GeckoProcessType_GPU:
|
||||
type = mozilla::ProcType::GPU;
|
||||
break;
|
||||
case GeckoProcessType::GeckoProcessType_VR:
|
||||
type = mozilla::ProcType::VR;
|
||||
break;
|
||||
case GeckoProcessType::GeckoProcessType_RDD:
|
||||
type = mozilla::ProcType::RDD;
|
||||
break;
|
||||
case GeckoProcessType::GeckoProcessType_Socket:
|
||||
type = mozilla::ProcType::Socket;
|
||||
break;
|
||||
case GeckoProcessType::GeckoProcessType_RemoteSandboxBroker:
|
||||
type = mozilla::ProcType::RemoteSandboxBroker;
|
||||
break;
|
||||
#ifdef MOZ_ENABLE_FORKSERVER
|
||||
case GeckoProcessType::GeckoProcessType_ForkServer:
|
||||
type = mozilla::ProcType::ForkServer;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
// Leave the default Unknown value in |type|.
|
||||
break;
|
||||
}
|
||||
|
||||
requests.EmplaceBack(
|
||||
/* aPid = */ childPid,
|
||||
/* aProcessType = */ type,
|
||||
/* aOrigin = */ origin,
|
||||
/* aChild = */ childId
|
||||
#ifdef XP_MACOSX
|
||||
,
|
||||
/* aChildTask = */ aGeckoProcess->GetChildTask()
|
||||
#endif // XP_MACOSX
|
||||
);
|
||||
});
|
||||
|
||||
// Now place background request.
|
||||
base::ProcessId parentPid = base::GetCurrentProcId();
|
||||
RefPtr<nsISerialEventTarget> target =
|
||||
global->EventTargetFor(TaskCategory::Performance);
|
||||
mozilla::GetProcInfo(std::move(requests))
|
||||
|
||||
// Getting the parent proc info
|
||||
mozilla::GetProcInfo(parentPid, 0, mozilla::ProcType::Browser, ""_ns)
|
||||
->Then(
|
||||
target, __func__,
|
||||
[target,
|
||||
domPromise](const HashMap<base::ProcessId, ProcInfo>& aSysProcInfo) {
|
||||
ParentProcInfoDictionary parentInfo;
|
||||
if (aSysProcInfo.count() == 0) {
|
||||
// For some reason, we couldn't get *any* info.
|
||||
// Maybe a sandboxing issue?
|
||||
domPromise->MaybeReject(NS_ERROR_UNEXPECTED);
|
||||
return;
|
||||
}
|
||||
nsTArray<ChildProcInfoDictionary> childrenInfo(
|
||||
aSysProcInfo.count() - 1);
|
||||
for (auto iter = aSysProcInfo.iter(); !iter.done(); iter.next()) {
|
||||
const auto& sysProcInfo = iter.get().value();
|
||||
nsresult rv;
|
||||
if (sysProcInfo.type == ProcType::Browser) {
|
||||
rv = mozilla::CopySysProcInfoToDOM(sysProcInfo, &parentInfo);
|
||||
if (NS_FAILED(rv)) {
|
||||
// Failing to copy? That's probably not something from we can
|
||||
// (or should) try to recover gracefully.
|
||||
domPromise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
|
||||
return;
|
||||
}
|
||||
MOZ_ASSERT(sysProcInfo.childId == 0);
|
||||
MOZ_ASSERT(sysProcInfo.origin.IsEmpty());
|
||||
} else {
|
||||
mozilla::dom::ChildProcInfoDictionary* childInfo =
|
||||
childrenInfo.AppendElement(fallible);
|
||||
if (!childInfo) {
|
||||
domPromise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
|
||||
return;
|
||||
}
|
||||
rv = mozilla::CopySysProcInfoToDOM(sysProcInfo, childInfo);
|
||||
if (NS_FAILED(rv)) {
|
||||
domPromise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
|
||||
return;
|
||||
}
|
||||
// Copy Firefox info.
|
||||
childInfo->mChildID = sysProcInfo.childId;
|
||||
childInfo->mOrigin = sysProcInfo.origin;
|
||||
childInfo->mType = ProcTypeToWebIDL(sysProcInfo.type);
|
||||
}
|
||||
}
|
||||
[target, domPromise, parentPid](ProcInfo aParentInfo) {
|
||||
// Get a list of ContentParent
|
||||
nsTArray<ContentParent*> contentParents;
|
||||
ContentParent::GetAll(contentParents);
|
||||
nsTArray<RefPtr<ProcInfoPromise>> promises;
|
||||
mozilla::ipc::GeckoChildProcessHost::GetAll(
|
||||
[&promises, &contentParents](
|
||||
mozilla::ipc::GeckoChildProcessHost* aGeckoProcess) {
|
||||
if (!aGeckoProcess->GetChildProcessHandle()) {
|
||||
return;
|
||||
}
|
||||
nsAutoCString origin;
|
||||
base::ProcessId childPid =
|
||||
base::GetProcId(aGeckoProcess->GetChildProcessHandle());
|
||||
int32_t childId = 0;
|
||||
mozilla::ProcType type = mozilla::ProcType::Unknown;
|
||||
switch (aGeckoProcess->GetProcessType()) {
|
||||
case GeckoProcessType::GeckoProcessType_Content: {
|
||||
ContentParent* contentParent = nullptr;
|
||||
// This loop can become slow as we get more processes in
|
||||
// Fission, so might need some refactoring in the future.
|
||||
for (ContentParent* parent : contentParents) {
|
||||
// find the match
|
||||
if (parent->Process() == aGeckoProcess) {
|
||||
contentParent = parent;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!contentParent) {
|
||||
return;
|
||||
}
|
||||
// Converting the remoteType into a ProcType.
|
||||
// Ideally, the remoteType should be strongly typed
|
||||
// upstream, this would make the conversion less brittle.
|
||||
nsAutoCString remoteType(contentParent->GetRemoteType());
|
||||
if (StringBeginsWith(remoteType,
|
||||
FISSION_WEB_REMOTE_TYPE)) {
|
||||
// WARNING: Do not change the order, as
|
||||
// `DEFAULT_REMOTE_TYPE` is a prefix of
|
||||
// `FISSION_WEB_REMOTE_TYPE`.
|
||||
type = mozilla::ProcType::WebIsolated;
|
||||
} else if (StringBeginsWith(remoteType,
|
||||
DEFAULT_REMOTE_TYPE)) {
|
||||
type = mozilla::ProcType::Web;
|
||||
} else if (remoteType == FILE_REMOTE_TYPE) {
|
||||
type = mozilla::ProcType::File;
|
||||
} else if (remoteType == EXTENSION_REMOTE_TYPE) {
|
||||
type = mozilla::ProcType::Extension;
|
||||
} else if (remoteType == PRIVILEGEDABOUT_REMOTE_TYPE) {
|
||||
type = mozilla::ProcType::PrivilegedAbout;
|
||||
} else if (remoteType == PRIVILEGEDMOZILLA_REMOTE_TYPE) {
|
||||
type = mozilla::ProcType::PrivilegedMozilla;
|
||||
} else if (StringBeginsWith(
|
||||
remoteType,
|
||||
WITH_COOP_COEP_REMOTE_TYPE_PREFIX)) {
|
||||
type = mozilla::ProcType::WebCOOPCOEP;
|
||||
} else if (remoteType == LARGE_ALLOCATION_REMOTE_TYPE) {
|
||||
type = mozilla::ProcType::WebLargeAllocation;
|
||||
} else if (remoteType == PREALLOC_REMOTE_TYPE) {
|
||||
type = mozilla::ProcType::Preallocated;
|
||||
} else {
|
||||
MOZ_CRASH("Unknown remoteType");
|
||||
}
|
||||
|
||||
// Attach the children to the parent.
|
||||
mozilla::dom::Sequence<mozilla::dom::ChildProcInfoDictionary>
|
||||
children(std::move(childrenInfo));
|
||||
parentInfo.mChildren = std::move(children);
|
||||
domPromise->MaybeResolve(parentInfo);
|
||||
// By convention, everything after '=' is the origin.
|
||||
nsACString::const_iterator cursor;
|
||||
nsACString::const_iterator end;
|
||||
remoteType.BeginReading(cursor);
|
||||
remoteType.EndReading(end);
|
||||
if (FindCharInReadable('=', cursor, end)) {
|
||||
origin = Substring(++cursor, end);
|
||||
}
|
||||
childId = contentParent->ChildID();
|
||||
break;
|
||||
}
|
||||
case GeckoProcessType::GeckoProcessType_Default:
|
||||
type = mozilla::ProcType::Browser;
|
||||
break;
|
||||
case GeckoProcessType::GeckoProcessType_Plugin:
|
||||
type = mozilla::ProcType::Plugin;
|
||||
break;
|
||||
case GeckoProcessType::GeckoProcessType_GMPlugin:
|
||||
type = mozilla::ProcType::GMPlugin;
|
||||
break;
|
||||
case GeckoProcessType::GeckoProcessType_GPU:
|
||||
type = mozilla::ProcType::GPU;
|
||||
break;
|
||||
case GeckoProcessType::GeckoProcessType_VR:
|
||||
type = mozilla::ProcType::VR;
|
||||
break;
|
||||
case GeckoProcessType::GeckoProcessType_RDD:
|
||||
type = mozilla::ProcType::RDD;
|
||||
break;
|
||||
case GeckoProcessType::GeckoProcessType_Socket:
|
||||
type = mozilla::ProcType::Socket;
|
||||
break;
|
||||
case GeckoProcessType::GeckoProcessType_RemoteSandboxBroker:
|
||||
type = mozilla::ProcType::RemoteSandboxBroker;
|
||||
break;
|
||||
#ifdef MOZ_ENABLE_FORKSERVER
|
||||
case GeckoProcessType::GeckoProcessType_ForkServer:
|
||||
type = mozilla::ProcType::ForkServer;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
// Leave the default Unknown value in |type|.
|
||||
break;
|
||||
}
|
||||
|
||||
promises.AppendElement(
|
||||
#ifdef XP_MACOSX
|
||||
mozilla::GetProcInfo(childPid, childId, type, origin,
|
||||
aGeckoProcess->GetChildTask())
|
||||
#else
|
||||
mozilla::GetProcInfo(childPid, childId, type, origin)
|
||||
#endif
|
||||
);
|
||||
});
|
||||
|
||||
auto ProcInfoResolver =
|
||||
[domPromise, parentPid, parentInfo = aParentInfo](
|
||||
const nsTArray<ProcInfo>& aChildrenInfo) {
|
||||
mozilla::dom::ParentProcInfoDictionary procInfo;
|
||||
// parent, basic info.
|
||||
procInfo.mPid = parentPid;
|
||||
procInfo.mFilename.Assign(parentInfo.filename);
|
||||
procInfo.mType = mozilla::dom::WebIDLProcType::Browser;
|
||||
procInfo.mVirtualMemorySize = parentInfo.virtualMemorySize;
|
||||
procInfo.mResidentSetSize = parentInfo.residentSetSize;
|
||||
procInfo.mCpuUser = parentInfo.cpuUser;
|
||||
procInfo.mCpuKernel = parentInfo.cpuKernel;
|
||||
|
||||
// parent, threads info.
|
||||
mozilla::dom::Sequence<mozilla::dom::ThreadInfoDictionary>
|
||||
threads;
|
||||
for (const ThreadInfo& entry : parentInfo.threads) {
|
||||
ThreadInfoDictionary* thread =
|
||||
threads.AppendElement(fallible);
|
||||
if (NS_WARN_IF(!thread)) {
|
||||
domPromise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
|
||||
return;
|
||||
}
|
||||
thread->mCpuUser = entry.cpuUser;
|
||||
thread->mCpuKernel = entry.cpuKernel;
|
||||
thread->mTid = entry.tid;
|
||||
thread->mName.Assign(entry.name);
|
||||
}
|
||||
procInfo.mThreads = std::move(threads);
|
||||
|
||||
mozilla::dom::Sequence<mozilla::dom::ChildProcInfoDictionary>
|
||||
children;
|
||||
for (const ProcInfo& info : aChildrenInfo) {
|
||||
ChildProcInfoDictionary* childProcInfo =
|
||||
children.AppendElement(fallible);
|
||||
if (NS_WARN_IF(!childProcInfo)) {
|
||||
domPromise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
|
||||
return;
|
||||
}
|
||||
// Basic info.
|
||||
childProcInfo->mChildID = info.childId;
|
||||
childProcInfo->mType = ProcTypeToWebIDL(info.type);
|
||||
childProcInfo->mOrigin = info.origin;
|
||||
childProcInfo->mPid = info.pid;
|
||||
childProcInfo->mFilename.Assign(info.filename);
|
||||
childProcInfo->mVirtualMemorySize = info.virtualMemorySize;
|
||||
childProcInfo->mResidentSetSize = info.residentSetSize;
|
||||
childProcInfo->mCpuUser = info.cpuUser;
|
||||
childProcInfo->mCpuKernel = info.cpuKernel;
|
||||
|
||||
// Threads info.
|
||||
mozilla::dom::Sequence<mozilla::dom::ThreadInfoDictionary>
|
||||
threads;
|
||||
for (const ThreadInfo& entry : info.threads) {
|
||||
ThreadInfoDictionary* thread =
|
||||
threads.AppendElement(fallible);
|
||||
if (NS_WARN_IF(!thread)) {
|
||||
domPromise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
|
||||
return;
|
||||
}
|
||||
thread->mCpuUser = entry.cpuUser;
|
||||
thread->mCpuKernel = entry.cpuKernel;
|
||||
thread->mTid = entry.tid;
|
||||
thread->mName.Assign(entry.name);
|
||||
}
|
||||
childProcInfo->mThreads = std::move(threads);
|
||||
}
|
||||
procInfo.mChildren = std::move(children);
|
||||
domPromise->MaybeResolve(procInfo);
|
||||
}; // end of ProcInfoResolver
|
||||
|
||||
ProcInfoPromise::All(target, promises)
|
||||
->Then(target, __func__, std::move(ProcInfoResolver),
|
||||
[domPromise](const nsresult aResult) {
|
||||
domPromise->MaybeReject(aResult);
|
||||
}); // end of ProcInfoPromise::All
|
||||
},
|
||||
[domPromise](nsresult aRv) { domPromise->MaybeReject(aRv); });
|
||||
MOZ_ASSERT(domPromise);
|
||||
[domPromise](nsresult aRv) {
|
||||
domPromise->MaybeReject(aRv);
|
||||
}); // end of mozilla::GetProcInfo
|
||||
|
||||
// sending back the promise instance
|
||||
return domPromise.forget();
|
||||
|
|
|
@ -238,16 +238,6 @@ class ContentParent final
|
|||
const nsACString& aRemoteType, BrowsingContext* aBrowsingContext,
|
||||
ContentParent* aOpenerContentParent);
|
||||
|
||||
/**
|
||||
* Get all content parents.
|
||||
*
|
||||
* # Lifetime
|
||||
*
|
||||
* These pointers are ONLY valid for synchronous use from the main thread.
|
||||
*
|
||||
* Do NOT attempt to use them after the main thread has had a chance to handle
|
||||
* messages or you could end up with dangling pointers.
|
||||
*/
|
||||
static void GetAll(nsTArray<ContentParent*>& aArray);
|
||||
|
||||
static void GetAllEvenIfDead(nsTArray<ContentParent*>& aArray);
|
||||
|
|
|
@ -180,8 +180,6 @@ class GeckoChildProcessHost : public ChildProcessHost,
|
|||
// Iterates over all instances and calls aCallback with each one of them.
|
||||
// This method will lock any addition/removal of new processes
|
||||
// so you need to make sure the callback is as fast as possible.
|
||||
//
|
||||
// To reiterate: the callbacks are executed synchronously.
|
||||
static void GetAll(const GeckoProcessCallback& aCallback);
|
||||
|
||||
friend class BaseProcessLauncher;
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
#include <base/process.h>
|
||||
#include <stdint.h>
|
||||
#include "mozilla/dom/ipc/IdType.h"
|
||||
#include "mozilla/HashTable.h"
|
||||
#include "mozilla/MozPromise.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
@ -84,117 +83,24 @@ struct ProcInfo {
|
|||
CopyableTArray<ThreadInfo> threads;
|
||||
};
|
||||
|
||||
typedef MozPromise<mozilla::HashMap<base::ProcessId, ProcInfo>, nsresult, true>
|
||||
ProcInfoPromise;
|
||||
typedef MozPromise<ProcInfo, nsresult, true> ProcInfoPromise;
|
||||
|
||||
/**
|
||||
* Data we need to request process info (e.g. CPU usage, memory usage)
|
||||
* from the operating system and populate the resulting `ProcInfo`.
|
||||
/*
|
||||
* GetProcInfo() uses a background thread to perform system calls.
|
||||
*
|
||||
* Note that this structure contains a mix of:
|
||||
* - low-level handles that we need to request low-level process info
|
||||
* (`aChildTask` on macOS, `aPid` on other platforms); and
|
||||
* - high-level data that we already acquired while looking for
|
||||
* `aPid`/`aChildTask` and that we will need further down the road.
|
||||
* Depending on the platform, this call can be quite expensive and the
|
||||
* promise may return after several ms.
|
||||
*/
|
||||
struct ProcInfoRequest {
|
||||
ProcInfoRequest(base::ProcessId aPid, ProcType aProcessType,
|
||||
const nsACString& aOrigin, uint32_t aChildId = 0
|
||||
#ifdef XP_MACOSX
|
||||
,
|
||||
mach_port_t aChildTask = 0
|
||||
#endif // XP_MACOSX
|
||||
)
|
||||
: pid(aPid),
|
||||
processType(aProcessType),
|
||||
origin(aOrigin),
|
||||
childId(aChildId)
|
||||
#ifdef XP_MACOSX
|
||||
,
|
||||
childTask(aChildTask)
|
||||
#endif // XP_MACOSX
|
||||
{
|
||||
}
|
||||
const base::ProcessId pid;
|
||||
const ProcType processType;
|
||||
const nsCString origin;
|
||||
// If the process is a child, its child id, otherwise `0`.
|
||||
const int32_t childId;
|
||||
#ifdef XP_MACOSX
|
||||
const mach_port_t childTask;
|
||||
#endif // XP_MACOSX
|
||||
};
|
||||
|
||||
/**
|
||||
* Batch a request for low-level information on Gecko processes.
|
||||
*
|
||||
* # Request
|
||||
*
|
||||
* Argument `aRequests` is a list of processes, along with high-level data
|
||||
* we have already obtained on them and that we need to populate the
|
||||
* resulting array of `ProcInfo`.
|
||||
*
|
||||
* # Result
|
||||
*
|
||||
* This call succeeds (possibly with missing data, see below) unless we
|
||||
* cannot allocate memory.
|
||||
*
|
||||
* # Performance
|
||||
*
|
||||
* - This call is always executed on a background thread.
|
||||
* - This call does NOT wake up children processes.
|
||||
* - This function is sometimes observably slow to resolve, in particular
|
||||
* under Windows.
|
||||
*
|
||||
* # Error-handling and race conditions
|
||||
*
|
||||
* Requesting low-level information on a process and its threads is inherently
|
||||
* subject to race conditions. Typically, if a process or a thread is killed
|
||||
* while we're preparing to fetch information, we can easily end up with
|
||||
* system/lib calls that return failures.
|
||||
*
|
||||
* For this reason, this API assumes that errors when placing a system/lib call
|
||||
* are likely and normal. When some information cannot be obtained, the API will
|
||||
* simply skip over said information.
|
||||
*
|
||||
* Note that due to different choices by OSes, the exact information we skip may
|
||||
* vary across platforms. For instance, under Unix, failing to access the
|
||||
* threads of a process will cause us to skip all data on the process, while
|
||||
* under Windows, process information will be returned without thread
|
||||
* information.
|
||||
*/
|
||||
RefPtr<ProcInfoPromise> GetProcInfo(nsTArray<ProcInfoRequest>&& aRequests);
|
||||
|
||||
/**
|
||||
* Utility function: copy data from a `ProcInfo` and into either a
|
||||
* `ParentProcInfoDictionary` or a `ChildProcInfoDictionary`.
|
||||
*/
|
||||
template <typename T>
|
||||
nsresult CopySysProcInfoToDOM(const ProcInfo& source, T* dest) {
|
||||
// Copy system info.
|
||||
dest->mPid = source.pid;
|
||||
dest->mFilename.Assign(source.filename);
|
||||
dest->mVirtualMemorySize = source.virtualMemorySize;
|
||||
dest->mResidentSetSize = source.residentSetSize;
|
||||
dest->mCpuUser = source.cpuUser;
|
||||
dest->mCpuKernel = source.cpuKernel;
|
||||
|
||||
// Copy thread info.
|
||||
mozilla::dom::Sequence<mozilla::dom::ThreadInfoDictionary> threads;
|
||||
for (const ThreadInfo& entry : source.threads) {
|
||||
mozilla::dom::ThreadInfoDictionary* thread =
|
||||
threads.AppendElement(fallible);
|
||||
if (NS_WARN_IF(!thread)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
thread->mCpuUser = entry.cpuUser;
|
||||
thread->mCpuKernel = entry.cpuKernel;
|
||||
thread->mTid = entry.tid;
|
||||
thread->mName.Assign(entry.name);
|
||||
}
|
||||
dest->mThreads = std::move(threads);
|
||||
return NS_OK;
|
||||
}
|
||||
RefPtr<ProcInfoPromise> GetProcInfo(base::ProcessId pid, int32_t childId,
|
||||
const ProcType& processType,
|
||||
const nsACString& origin,
|
||||
mach_port_t aChildTask = MACH_PORT_NULL);
|
||||
#else
|
||||
RefPtr<ProcInfoPromise> GetProcInfo(base::ProcessId pid, int32_t childId,
|
||||
const ProcType& processType,
|
||||
const nsACString& origin);
|
||||
#endif
|
||||
|
||||
} // namespace mozilla
|
||||
#endif // ProcInfo_h
|
||||
|
|
|
@ -9,7 +9,9 @@
|
|||
|
||||
namespace mozilla {
|
||||
|
||||
RefPtr<ProcInfoPromise> GetProcInfo(nsTArray<ProcInfoRequest>&& aRequests) {
|
||||
RefPtr<ProcInfoPromise> GetProcInfo(base::ProcessId pid, int32_t childId,
|
||||
const ProcType& type,
|
||||
const nsACString& origin) {
|
||||
// Not implemented on Android.
|
||||
return ProcInfoPromise::CreateAndReject(NS_ERROR_NOT_IMPLEMENTED, __func__);
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "mozilla/ProcInfo.h"
|
||||
#include "mozilla/ScopeExit.h"
|
||||
#include "mozilla/ipc/GeckoChildProcessHost.h"
|
||||
|
||||
#include "nsNetCID.h"
|
||||
|
@ -20,9 +19,11 @@
|
|||
|
||||
namespace mozilla {
|
||||
|
||||
RefPtr<ProcInfoPromise> GetProcInfo(nsTArray<ProcInfoRequest>&& aRequests) {
|
||||
RefPtr<ProcInfoPromise> GetProcInfo(base::ProcessId pid, int32_t childId, const ProcType& type,
|
||||
const nsACString& origin, mach_port_t aChildTask) {
|
||||
auto holder = MakeUnique<MozPromiseHolder<ProcInfoPromise>>();
|
||||
RefPtr<ProcInfoPromise> promise = holder->Ensure(__func__);
|
||||
|
||||
nsresult rv = NS_OK;
|
||||
nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
|
||||
if (NS_FAILED(rv)) {
|
||||
|
@ -31,115 +32,93 @@ 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);
|
||||
struct proc_bsdinfo proc;
|
||||
if ((unsigned long)proc_pidinfo(request.pid, PROC_PIDTBSDINFO, 0, &proc,
|
||||
PROC_PIDTBSDINFO_SIZE) < PROC_PIDTBSDINFO_SIZE) {
|
||||
// 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;
|
||||
}
|
||||
// Ensure that the string is still alive when `ResolveGetProcInfo` is called.
|
||||
nsCString originCopy(origin);
|
||||
auto ResolveGetProcinfo = [holder = std::move(holder), pid, type,
|
||||
originCopy = std::move(originCopy), childId, aChildTask]() {
|
||||
ProcInfo info;
|
||||
info.pid = pid;
|
||||
info.childId = childId;
|
||||
info.type = type;
|
||||
info.origin = originCopy;
|
||||
struct proc_bsdinfo proc;
|
||||
if ((unsigned long)proc_pidinfo(pid, PROC_PIDTBSDINFO, 0, &proc, PROC_PIDTBSDINFO_SIZE) <
|
||||
PROC_PIDTBSDINFO_SIZE) {
|
||||
holder->Reject(NS_ERROR_FAILURE, __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
struct proc_taskinfo pti;
|
||||
if ((unsigned long)proc_pidinfo(request.pid, PROC_PIDTASKINFO, 0, &pti,
|
||||
PROC_PIDTASKINFO_SIZE) < PROC_PIDTASKINFO_SIZE) {
|
||||
continue;
|
||||
}
|
||||
struct proc_taskinfo pti;
|
||||
if ((unsigned long)proc_pidinfo(pid, PROC_PIDTASKINFO, 0, &pti, PROC_PIDTASKINFO_SIZE) <
|
||||
PROC_PIDTASKINFO_SIZE) {
|
||||
holder->Reject(NS_ERROR_FAILURE, __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
// copying all the info to the ProcInfo struct
|
||||
info.filename.AssignASCII(proc.pbi_name);
|
||||
info.virtualMemorySize = pti.pti_virtual_size;
|
||||
info.residentSetSize = pti.pti_resident_size;
|
||||
info.cpuUser = pti.pti_total_user;
|
||||
info.cpuKernel = pti.pti_total_system;
|
||||
// copying all the info to the ProcInfo struct
|
||||
info.filename.AssignASCII(proc.pbi_name);
|
||||
info.virtualMemorySize = pti.pti_virtual_size;
|
||||
info.residentSetSize = pti.pti_resident_size;
|
||||
info.cpuUser = pti.pti_total_user;
|
||||
info.cpuKernel = pti.pti_total_system;
|
||||
|
||||
// Now getting threads info
|
||||
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;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
// Now getting threads info
|
||||
mach_port_t selectedTask;
|
||||
// If we did not get a task from a child process, we use mach_task_self()
|
||||
if (aChildTask == MACH_PORT_NULL) {
|
||||
selectedTask = mach_task_self();
|
||||
} else {
|
||||
selectedTask = aChildTask;
|
||||
}
|
||||
// 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) {
|
||||
holder->Resolve(info, __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
auto guardThreadCount = MakeScopeExit([&] {
|
||||
vm_deallocate(selectedTask, (vm_address_t)threadList, sizeof(thread_t) * threadCount);
|
||||
});
|
||||
mach_msg_type_number_t count;
|
||||
|
||||
mach_msg_type_number_t count;
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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->cpuUser = threadInfoData.pth_user_time;
|
||||
thread->cpuKernel = 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__);
|
||||
});
|
||||
// The two system calls were successful, let's add that thread
|
||||
ThreadInfo thread;
|
||||
thread.cpuUser = threadInfoData.pth_user_time;
|
||||
thread.cpuKernel = threadInfoData.pth_system_time;
|
||||
thread.name.AssignASCII(threadInfoData.pth_name);
|
||||
thread.tid = identifierInfo.thread_id;
|
||||
info.threads.AppendElement(thread);
|
||||
}
|
||||
holder->Resolve(info, __func__);
|
||||
};
|
||||
|
||||
RefPtr<nsIRunnable> r = NS_NewRunnableFunction(__func__, std::move(ResolveGetProcinfo));
|
||||
rv = target->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_WARNING("Failed to dispatch the LoadDataRunnable.");
|
||||
}
|
||||
return promise;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
}
|
||||
|
|
|
@ -209,7 +209,9 @@ class ThreadInfoReader final : public StatReader {
|
|||
base::ProcessId mTid;
|
||||
};
|
||||
|
||||
RefPtr<ProcInfoPromise> GetProcInfo(nsTArray<ProcInfoRequest>&& aRequests) {
|
||||
RefPtr<ProcInfoPromise> GetProcInfo(base::ProcessId pid, int32_t childId,
|
||||
const ProcType& type,
|
||||
const nsACString& origin) {
|
||||
auto holder = MakeUnique<MozPromiseHolder<ProcInfoPromise>>();
|
||||
RefPtr<ProcInfoPromise> promise = holder->Ensure(__func__);
|
||||
nsresult rv = NS_OK;
|
||||
|
@ -221,72 +223,56 @@ RefPtr<ProcInfoPromise> GetProcInfo(nsTArray<ProcInfoRequest>&& aRequests) {
|
|||
return promise;
|
||||
}
|
||||
|
||||
// Ensure that the string is still alive when the runnable is called.
|
||||
nsCString originCopy(origin);
|
||||
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__);
|
||||
__func__, [holder = std::move(holder), pid, type,
|
||||
originCopy = std::move(originCopy), childId]() {
|
||||
// opening the stat file and reading its content
|
||||
StatReader reader(pid);
|
||||
ProcInfo info;
|
||||
nsresult rv = reader.ParseProc(info);
|
||||
if (NS_FAILED(rv)) {
|
||||
holder->Reject(rv, __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;
|
||||
}
|
||||
// Extra info
|
||||
info.pid = request.pid;
|
||||
info.childId = request.childId;
|
||||
info.type = request.processType;
|
||||
info.origin = request.origin;
|
||||
// Extra info
|
||||
info.pid = pid;
|
||||
info.childId = childId;
|
||||
info.type = type;
|
||||
info.origin = originCopy;
|
||||
|
||||
// Let's look at the threads
|
||||
nsCString taskPath;
|
||||
taskPath.AppendPrintf("/proc/%u/task", request.pid);
|
||||
nsAutoRef<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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
// Let's look at the threads
|
||||
nsCString taskPath;
|
||||
taskPath.AppendPrintf("/proc/%u/task", pid);
|
||||
nsAutoRef<DIR> dirHandle(opendir(taskPath.get()));
|
||||
if (!dirHandle) {
|
||||
// No threads ? Let's stop here and ignore the problem.
|
||||
holder->Resolve(info, __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
// ... and we're done!
|
||||
holder->Resolve(std::move(gathered), __func__);
|
||||
// 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(pid, tid);
|
||||
ThreadInfo threadInfo;
|
||||
rv = reader.ParseThread(threadInfo);
|
||||
if (NS_FAILED(rv)) {
|
||||
continue;
|
||||
}
|
||||
info.threads.AppendElement(threadInfo);
|
||||
}
|
||||
holder->Resolve(info, __func__);
|
||||
});
|
||||
|
||||
rv = target->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
|
||||
|
|
|
@ -21,7 +21,63 @@ uint64_t ToNanoSeconds(const FILETIME& aFileTime) {
|
|||
return usec.QuadPart * 100;
|
||||
}
|
||||
|
||||
RefPtr<ProcInfoPromise> GetProcInfo(nsTArray<ProcInfoRequest>&& aRequests) {
|
||||
void AppendThreads(ProcInfo* info) {
|
||||
THREADENTRY32 te32;
|
||||
// `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"));
|
||||
|
||||
// Take a snapshot of all running threads, system-wide.
|
||||
nsAutoHandle hThreadSnap(CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0));
|
||||
if (!hThreadSnap) {
|
||||
return;
|
||||
}
|
||||
te32.dwSize = sizeof(THREADENTRY32);
|
||||
|
||||
// Retrieve information about the first thread,
|
||||
// and exit if unsuccessful
|
||||
if (!Thread32First(hThreadSnap.get(), &te32)) {
|
||||
return;
|
||||
}
|
||||
|
||||
do {
|
||||
if (te32.th32OwnerProcessID == info->pid) {
|
||||
nsAutoHandle hThread(
|
||||
OpenThread(THREAD_QUERY_INFORMATION, TRUE, te32.th32ThreadID));
|
||||
if (!hThread) {
|
||||
continue;
|
||||
}
|
||||
|
||||
FILETIME createTime, exitTime, kernelTime, userTime;
|
||||
if (!GetThreadTimes(hThread.get(), &createTime, &exitTime, &kernelTime,
|
||||
&userTime)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ThreadInfo thread;
|
||||
if (getThreadDescription) {
|
||||
PWSTR threadName = nullptr;
|
||||
if (getThreadDescription(hThread.get(), &threadName) && threadName) {
|
||||
thread.name = threadName;
|
||||
}
|
||||
if (threadName) {
|
||||
LocalFree(threadName);
|
||||
}
|
||||
}
|
||||
thread.tid = te32.th32ThreadID;
|
||||
thread.cpuKernel = ToNanoSeconds(kernelTime);
|
||||
thread.cpuUser = ToNanoSeconds(userTime);
|
||||
info->threads.AppendElement(thread);
|
||||
}
|
||||
} while (Thread32Next(hThreadSnap.get(), &te32));
|
||||
}
|
||||
|
||||
RefPtr<ProcInfoPromise> GetProcInfo(base::ProcessId pid, int32_t childId,
|
||||
const ProcType& type,
|
||||
const nsACString& origin) {
|
||||
auto holder = MakeUnique<MozPromiseHolder<ProcInfoPromise>>();
|
||||
RefPtr<ProcInfoPromise> promise = holder->Ensure(__func__);
|
||||
|
||||
|
@ -34,136 +90,50 @@ RefPtr<ProcInfoPromise> GetProcInfo(nsTArray<ProcInfoRequest>&& aRequests) {
|
|||
return promise;
|
||||
}
|
||||
|
||||
// Ensure that the string is still alive when `ResolveGetProcInfo` is called.
|
||||
nsCString originCopy(origin);
|
||||
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__);
|
||||
[holder = std::move(holder), originCopy = std::move(originCopy), pid,
|
||||
type, childId]() -> void {
|
||||
nsAutoHandle handle(OpenProcess(
|
||||
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid));
|
||||
|
||||
if (!handle) {
|
||||
holder->Reject(NS_ERROR_FAILURE, __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
// ---- 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;
|
||||
}
|
||||
|
||||
wchar_t filename[MAX_PATH];
|
||||
if (GetProcessImageFileNameW(handle.get(), filename, MAX_PATH) == 0) {
|
||||
// Ignore process, it may have died.
|
||||
continue;
|
||||
}
|
||||
FILETIME createTime, exitTime, kernelTime, userTime;
|
||||
if (!GetProcessTimes(handle.get(), &createTime, &exitTime,
|
||||
&kernelTime, &userTime)) {
|
||||
// Ignore process, it may have died.
|
||||
continue;
|
||||
}
|
||||
PROCESS_MEMORY_COUNTERS 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.filename.Assign(filename);
|
||||
info.cpuKernel = ToNanoSeconds(kernelTime);
|
||||
info.cpuUser = ToNanoSeconds(userTime);
|
||||
info.residentSetSize = memoryCounters.WorkingSetSize;
|
||||
info.virtualMemorySize = memoryCounters.PagefileUsage;
|
||||
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__);
|
||||
wchar_t filename[MAX_PATH];
|
||||
if (GetProcessImageFileNameW(handle.get(), filename, MAX_PATH) == 0) {
|
||||
holder->Reject(NS_ERROR_FAILURE, __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 attempt to find data
|
||||
// on other threads.
|
||||
continue;
|
||||
}
|
||||
|
||||
FILETIME createTime, exitTime, kernelTime, userTime;
|
||||
if (!GetThreadTimes(hThread.get(), &createTime, &exitTime,
|
||||
&kernelTime, &userTime)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (getThreadDescription) {
|
||||
PWSTR threadName = nullptr;
|
||||
if (getThreadDescription(hThread.get(), &threadName) &&
|
||||
threadName) {
|
||||
threadInfo->name = threadName;
|
||||
}
|
||||
if (threadName) {
|
||||
LocalFree(threadName);
|
||||
}
|
||||
}
|
||||
threadInfo->tid = te32.th32ThreadID;
|
||||
threadInfo->cpuKernel = ToNanoSeconds(kernelTime);
|
||||
threadInfo->cpuUser = ToNanoSeconds(userTime);
|
||||
FILETIME createTime, exitTime, kernelTime, userTime;
|
||||
if (!GetProcessTimes(handle.get(), &createTime, &exitTime, &kernelTime,
|
||||
&userTime)) {
|
||||
holder->Reject(NS_ERROR_FAILURE, __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
// ----- We're ready to return.
|
||||
holder->Resolve(std::move(gathered), __func__);
|
||||
PROCESS_MEMORY_COUNTERS memoryCounters;
|
||||
if (!GetProcessMemoryInfo(handle.get(),
|
||||
(PPROCESS_MEMORY_COUNTERS)&memoryCounters,
|
||||
sizeof(memoryCounters))) {
|
||||
holder->Reject(NS_ERROR_FAILURE, __func__);
|
||||
return;
|
||||
}
|
||||
ProcInfo info;
|
||||
info.pid = pid;
|
||||
info.childId = childId;
|
||||
info.type = type;
|
||||
info.origin = originCopy;
|
||||
info.filename.Assign(filename);
|
||||
info.cpuKernel = ToNanoSeconds(kernelTime);
|
||||
info.cpuUser = ToNanoSeconds(userTime);
|
||||
info.residentSetSize = memoryCounters.WorkingSetSize;
|
||||
info.virtualMemorySize = memoryCounters.PagefileUsage;
|
||||
AppendThreads(&info);
|
||||
holder->Resolve(info, __func__);
|
||||
});
|
||||
|
||||
rv = target->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
|
||||
|
|
Загрузка…
Ссылка в новой задаче