mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-27 20:25:44 +00:00
2420a8ebb9
This patch introduces a new module in widget that implements a simple API to retrieve system information about a process and its threads. This function is wrapped into ChromeUtils.RequestProcInfo to return information about processes started by Firefox. The use case for this API is to monitor Firefox resources usage in projects like the battery usage done by the data science team. Differential Revision: https://phabricator.services.mozilla.com/D10069 --HG-- extra : moz-landing-system : lando
333 lines
9.6 KiB
C++
333 lines
9.6 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "SocketProcessHost.h"
|
|
|
|
#include "nsAppRunner.h"
|
|
#include "nsIObserverService.h"
|
|
#include "SocketProcessParent.h"
|
|
|
|
#ifdef MOZ_GECKO_PROFILER
|
|
# include "ProfilerParent.h"
|
|
#endif
|
|
|
|
namespace mozilla {
|
|
namespace net {
|
|
|
|
#define NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC "ipc:network:set-offline"
|
|
|
|
class OfflineObserver final : public nsIObserver {
|
|
public:
|
|
NS_DECL_THREADSAFE_ISUPPORTS
|
|
explicit OfflineObserver(SocketProcessHost* aProcessHost)
|
|
: mProcessHost(aProcessHost) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
|
if (obs) {
|
|
obs->AddObserver(this, NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC, false);
|
|
obs->AddObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, false);
|
|
}
|
|
}
|
|
|
|
void Destroy() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
|
if (obs) {
|
|
obs->RemoveObserver(this, NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC);
|
|
}
|
|
mProcessHost = nullptr;
|
|
}
|
|
|
|
private:
|
|
// nsIObserver implementation.
|
|
NS_IMETHOD
|
|
Observe(nsISupports* aSubject, const char* aTopic,
|
|
const char16_t* aData) override {
|
|
if (!mProcessHost) {
|
|
return NS_OK;
|
|
}
|
|
|
|
if (!strcmp(aTopic, NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC)) {
|
|
NS_ConvertUTF16toUTF8 dataStr(aData);
|
|
const char* offline = dataStr.get();
|
|
if (!mProcessHost->IsConnected() ||
|
|
mProcessHost->GetActor()->SendSetOffline(
|
|
!strcmp(offline, "true") ? true : false)) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
} else if (!strcmp(aTopic, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID)) {
|
|
nsCOMPtr<nsIObserverService> obs =
|
|
mozilla::services::GetObserverService();
|
|
obs->RemoveObserver(this, NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC);
|
|
obs->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
virtual ~OfflineObserver() = default;
|
|
|
|
SocketProcessHost* mProcessHost;
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(OfflineObserver, nsIObserver)
|
|
|
|
SocketProcessHost::SocketProcessHost(Listener* aListener)
|
|
: GeckoChildProcessHost(GeckoProcessType_Socket),
|
|
mListener(aListener),
|
|
mTaskFactory(this),
|
|
mLaunchPhase(LaunchPhase::Unlaunched),
|
|
mShutdownRequested(false),
|
|
mChannelClosed(false) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_COUNT_CTOR(SocketProcessHost);
|
|
}
|
|
|
|
SocketProcessHost::~SocketProcessHost() {
|
|
MOZ_COUNT_DTOR(SocketProcessHost);
|
|
if (mOfflineObserver) {
|
|
RefPtr<OfflineObserver> observer = mOfflineObserver;
|
|
NS_DispatchToMainThread(
|
|
NS_NewRunnableFunction("SocketProcessHost::DestroyOfflineObserver",
|
|
[observer]() { observer->Destroy(); }));
|
|
}
|
|
}
|
|
|
|
bool SocketProcessHost::Launch() {
|
|
MOZ_ASSERT(mLaunchPhase == LaunchPhase::Unlaunched);
|
|
MOZ_ASSERT(!mSocketProcessParent);
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
std::vector<std::string> extraArgs;
|
|
|
|
nsAutoCString parentBuildID(mozilla::PlatformBuildID());
|
|
extraArgs.push_back("-parentBuildID");
|
|
extraArgs.push_back(parentBuildID.get());
|
|
|
|
SharedPreferenceSerializer prefSerializer;
|
|
if (!prefSerializer.SerializeToSharedMemory()) {
|
|
return false;
|
|
}
|
|
|
|
// Formats a pointer or pointer-sized-integer as a string suitable for passing
|
|
// in an arguments list.
|
|
auto formatPtrArg = [](auto arg) {
|
|
return nsPrintfCString("%zu", uintptr_t(arg));
|
|
};
|
|
|
|
#if defined(XP_WIN)
|
|
// Record the handle as to-be-shared, and pass it via a command flag. This
|
|
// works because Windows handles are system-wide.
|
|
HANDLE prefsHandle = prefSerializer.GetSharedMemoryHandle();
|
|
AddHandleToShare(prefsHandle);
|
|
AddHandleToShare(prefSerializer.GetPrefMapHandle().get());
|
|
extraArgs.push_back("-prefsHandle");
|
|
extraArgs.push_back(formatPtrArg(prefsHandle).get());
|
|
extraArgs.push_back("-prefMapHandle");
|
|
extraArgs.push_back(
|
|
formatPtrArg(prefSerializer.GetPrefMapHandle().get()).get());
|
|
#else
|
|
// In contrast, Unix fds are per-process. So remap the fd to a fixed one that
|
|
// will be used in the child.
|
|
// XXX: bug 1440207 is about improving how fixed fds are used.
|
|
//
|
|
// Note: on Android, AddFdToRemap() sets up the fd to be passed via a Parcel,
|
|
// and the fixed fd isn't used. However, we still need to mark it for
|
|
// remapping so it doesn't get closed in the child.
|
|
AddFdToRemap(prefSerializer.GetSharedMemoryHandle().fd, kPrefsFileDescriptor);
|
|
AddFdToRemap(prefSerializer.GetPrefMapHandle().get(), kPrefMapFileDescriptor);
|
|
#endif
|
|
|
|
// Pass the lengths via command line flags.
|
|
extraArgs.push_back("-prefsLen");
|
|
extraArgs.push_back(formatPtrArg(prefSerializer.GetPrefLength()).get());
|
|
extraArgs.push_back("-prefMapSize");
|
|
extraArgs.push_back(formatPtrArg(prefSerializer.GetPrefMapSize()).get());
|
|
|
|
mLaunchPhase = LaunchPhase::Waiting;
|
|
if (!GeckoChildProcessHost::LaunchAndWaitForProcessHandle(extraArgs)) {
|
|
mLaunchPhase = LaunchPhase::Complete;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void SocketProcessHost::OnChannelConnected(int32_t peer_pid) {
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
|
|
GeckoChildProcessHost::OnChannelConnected(peer_pid);
|
|
|
|
// Post a task to the main thread. Take the lock because mTaskFactory is not
|
|
// thread-safe.
|
|
RefPtr<Runnable> runnable;
|
|
{
|
|
MonitorAutoLock lock(mMonitor);
|
|
runnable = mTaskFactory.NewRunnableMethod(
|
|
&SocketProcessHost::OnChannelConnectedTask);
|
|
}
|
|
NS_DispatchToMainThread(runnable);
|
|
}
|
|
|
|
void SocketProcessHost::OnChannelError() {
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
|
|
GeckoChildProcessHost::OnChannelError();
|
|
|
|
// Post a task to the main thread. Take the lock because mTaskFactory is not
|
|
// thread-safe.
|
|
RefPtr<Runnable> runnable;
|
|
{
|
|
MonitorAutoLock lock(mMonitor);
|
|
runnable =
|
|
mTaskFactory.NewRunnableMethod(&SocketProcessHost::OnChannelErrorTask);
|
|
}
|
|
NS_DispatchToMainThread(runnable);
|
|
}
|
|
|
|
void SocketProcessHost::OnChannelConnectedTask() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (mLaunchPhase == LaunchPhase::Waiting) {
|
|
InitAfterConnect(true);
|
|
}
|
|
}
|
|
|
|
void SocketProcessHost::OnChannelErrorTask() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (mLaunchPhase == LaunchPhase::Waiting) {
|
|
InitAfterConnect(false);
|
|
}
|
|
}
|
|
|
|
void SocketProcessHost::InitAfterConnect(bool aSucceeded) {
|
|
MOZ_ASSERT(mLaunchPhase == LaunchPhase::Waiting);
|
|
MOZ_ASSERT(!mSocketProcessParent);
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
mLaunchPhase = LaunchPhase::Complete;
|
|
|
|
if (aSucceeded) {
|
|
mSocketProcessParent = MakeUnique<SocketProcessParent>(this);
|
|
DebugOnly<bool> rv = mSocketProcessParent->Open(
|
|
GetChannel(), base::GetProcId(GetChildProcessHandle()));
|
|
MOZ_ASSERT(rv);
|
|
|
|
nsCOMPtr<nsIIOService> ioService(do_GetIOService());
|
|
MOZ_ASSERT(ioService, "No IO service?");
|
|
bool offline = false;
|
|
DebugOnly<nsresult> result = ioService->GetOffline(&offline);
|
|
MOZ_ASSERT(NS_SUCCEEDED(result), "Failed getting offline?");
|
|
|
|
#ifdef MOZ_GECKO_PROFILER
|
|
Unused << GetActor()->SendInitProfiler(
|
|
ProfilerParent::CreateForProcess(GetActor()->OtherPid()));
|
|
#endif
|
|
|
|
Unused << GetActor()->SendSetOffline(offline);
|
|
|
|
mOfflineObserver = new OfflineObserver(this);
|
|
}
|
|
|
|
if (mListener) {
|
|
mListener->OnProcessLaunchComplete(this, aSucceeded);
|
|
}
|
|
}
|
|
|
|
void SocketProcessHost::Shutdown() {
|
|
MOZ_ASSERT(!mShutdownRequested);
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
mListener = nullptr;
|
|
if (mOfflineObserver) {
|
|
mOfflineObserver->Destroy();
|
|
mOfflineObserver = nullptr;
|
|
}
|
|
|
|
if (mSocketProcessParent) {
|
|
// OnChannelClosed uses this to check if the shutdown was expected or
|
|
// unexpected.
|
|
mShutdownRequested = true;
|
|
|
|
// The channel might already be closed if we got here unexpectedly.
|
|
if (!mChannelClosed) {
|
|
mSocketProcessParent->Close();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
DestroyProcess();
|
|
}
|
|
|
|
void SocketProcessHost::OnChannelClosed() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
mChannelClosed = true;
|
|
|
|
if (!mShutdownRequested && mListener) {
|
|
// This is an unclean shutdown. Notify our listener that we're going away.
|
|
mListener->OnProcessUnexpectedShutdown(this);
|
|
} else {
|
|
DestroyProcess();
|
|
}
|
|
|
|
// Release the actor.
|
|
SocketProcessParent::Destroy(std::move(mSocketProcessParent));
|
|
MOZ_ASSERT(!mSocketProcessParent);
|
|
}
|
|
|
|
void SocketProcessHost::DestroyProcess() {
|
|
{
|
|
MonitorAutoLock lock(mMonitor);
|
|
mTaskFactory.RevokeAll();
|
|
}
|
|
|
|
MessageLoop::current()->PostTask(NS_NewRunnableFunction(
|
|
"DestroySocketProcessRunnable", [this] { Destroy(); }));
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// SocketProcessMemoryReporter
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool SocketProcessMemoryReporter::IsAlive() const {
|
|
MOZ_ASSERT(gIOService);
|
|
|
|
if (!gIOService->mSocketProcess) {
|
|
return false;
|
|
}
|
|
|
|
return gIOService->mSocketProcess->IsConnected();
|
|
}
|
|
|
|
bool SocketProcessMemoryReporter::SendRequestMemoryReport(
|
|
const uint32_t& aGeneration, const bool& aAnonymize,
|
|
const bool& aMinimizeMemoryUsage, const dom::MaybeFileDesc& aDMDFile) {
|
|
MOZ_ASSERT(gIOService);
|
|
|
|
if (!gIOService->mSocketProcess) {
|
|
return false;
|
|
}
|
|
|
|
SocketProcessParent* actor = gIOService->mSocketProcess->GetActor();
|
|
if (!actor) {
|
|
return false;
|
|
}
|
|
|
|
return actor->SendRequestMemoryReport(aGeneration, aAnonymize,
|
|
aMinimizeMemoryUsage, aDMDFile);
|
|
}
|
|
|
|
int32_t SocketProcessMemoryReporter::Pid() const {
|
|
MOZ_ASSERT(gIOService);
|
|
return gIOService->SocketProcessPid();
|
|
}
|
|
|
|
} // namespace net
|
|
} // namespace mozilla
|