зеркало из https://github.com/mozilla/gecko-dev.git
310 строки
9.9 KiB
C++
310 строки
9.9 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* 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 "nsContentUtils.h"
|
|
#include "nsThreadUtils.h"
|
|
#include "mozilla/AbstractThread.h"
|
|
#include "mozilla/Logging.h"
|
|
#include "mozilla/PerformanceUtils.h"
|
|
#include "mozilla/PerformanceMetricsCollector.h"
|
|
#include "mozilla/StaticPrefs_dom.h"
|
|
#include "mozilla/TaskQueue.h"
|
|
#include "mozilla/dom/ContentParent.h"
|
|
#include "mozilla/dom/Promise.h"
|
|
#include "mozilla/dom/WorkerDebugger.h"
|
|
#include "mozilla/dom/WorkerDebuggerManager.h"
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::dom;
|
|
|
|
static mozilla::LazyLogModule sPerfLog("PerformanceMetricsCollector");
|
|
#ifdef LOG
|
|
# undef LOG
|
|
#endif
|
|
#define LOG(args) MOZ_LOG(sPerfLog, mozilla::LogLevel::Debug, args)
|
|
|
|
namespace mozilla {
|
|
|
|
//
|
|
// class IPCTimeout
|
|
//
|
|
NS_IMPL_ISUPPORTS(IPCTimeout, nsIObserver)
|
|
|
|
// static
|
|
IPCTimeout* IPCTimeout::CreateInstance(AggregatedResults* aResults) {
|
|
MOZ_ASSERT(aResults);
|
|
uint32_t delay = StaticPrefs::dom_performance_children_results_ipc_timeout();
|
|
if (delay == 0) {
|
|
return nullptr;
|
|
}
|
|
return new IPCTimeout(aResults, delay);
|
|
}
|
|
|
|
IPCTimeout::IPCTimeout(AggregatedResults* aResults, uint32_t aDelay)
|
|
: mResults(aResults) {
|
|
MOZ_ASSERT(aResults);
|
|
MOZ_ASSERT(aDelay > 0);
|
|
mozilla::DebugOnly<nsresult> rv = NS_NewTimerWithObserver(
|
|
getter_AddRefs(mTimer), this, aDelay, nsITimer::TYPE_ONE_SHOT);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
LOG(("IPCTimeout timer created"));
|
|
}
|
|
|
|
IPCTimeout::~IPCTimeout() { Cancel(); }
|
|
|
|
void IPCTimeout::Cancel() {
|
|
if (mTimer) {
|
|
LOG(("IPCTimeout timer canceled"));
|
|
mTimer->Cancel();
|
|
mTimer = nullptr;
|
|
}
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
IPCTimeout::Observe(nsISupports* aSubject, const char* aTopic,
|
|
const char16_t* aData) {
|
|
MOZ_ASSERT(strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC) == 0);
|
|
LOG(("IPCTimeout timer triggered"));
|
|
mResults->ResolveNow();
|
|
return NS_OK;
|
|
}
|
|
|
|
//
|
|
// class AggregatedResults
|
|
//
|
|
AggregatedResults::AggregatedResults(nsID aUUID,
|
|
PerformanceMetricsCollector* aCollector)
|
|
: mPendingResults(0), mCollector(aCollector), mUUID(aUUID) {
|
|
MOZ_ASSERT(aCollector);
|
|
mIPCTimeout = IPCTimeout::CreateInstance(this);
|
|
}
|
|
|
|
void AggregatedResults::Abort(nsresult aReason) {
|
|
MOZ_ASSERT(!mHolder.IsEmpty());
|
|
MOZ_ASSERT(NS_FAILED(aReason));
|
|
if (mIPCTimeout) {
|
|
mIPCTimeout->Cancel();
|
|
mIPCTimeout = nullptr;
|
|
}
|
|
mHolder.Reject(aReason, __func__);
|
|
mPendingResults = 0;
|
|
}
|
|
|
|
void AggregatedResults::ResolveNow() {
|
|
MOZ_ASSERT(!mHolder.IsEmpty());
|
|
LOG(("[%s] Early resolve", nsIDToCString(mUUID).get()));
|
|
mHolder.Resolve(mData, __func__);
|
|
mIPCTimeout = nullptr;
|
|
mCollector->ForgetAggregatedResults(mUUID);
|
|
}
|
|
|
|
void AggregatedResults::AppendResult(
|
|
const nsTArray<dom::PerformanceInfo>& aMetrics) {
|
|
if (mHolder.IsEmpty()) {
|
|
// A previous call failed and the promise was already rejected
|
|
return;
|
|
}
|
|
MOZ_ASSERT(mPendingResults > 0);
|
|
|
|
// Each PerformanceInfo is converted into a PerformanceInfoDictionary
|
|
for (const PerformanceInfo& result : aMetrics) {
|
|
mozilla::dom::Sequence<mozilla::dom::CategoryDispatchDictionary> items;
|
|
|
|
for (const CategoryDispatch& entry : result.items()) {
|
|
uint32_t count = entry.count();
|
|
if (count == 0) {
|
|
continue;
|
|
}
|
|
CategoryDispatchDictionary* item = items.AppendElement(fallible);
|
|
if (NS_WARN_IF(!item)) {
|
|
Abort(NS_ERROR_OUT_OF_MEMORY);
|
|
return;
|
|
}
|
|
item->mCategory = entry.category();
|
|
item->mCount = count;
|
|
}
|
|
|
|
PerformanceInfoDictionary* data = mData.AppendElement(fallible);
|
|
if (NS_WARN_IF(!data)) {
|
|
Abort(NS_ERROR_OUT_OF_MEMORY);
|
|
return;
|
|
}
|
|
data->mPid = result.pid();
|
|
data->mWindowId = result.windowId();
|
|
data->mHost.Assign(result.host());
|
|
data->mDuration = result.duration();
|
|
data->mCounterId = result.counterId();
|
|
data->mIsWorker = result.isWorker();
|
|
data->mIsTopLevel = result.isTopLevel();
|
|
data->mMemoryInfo.mDomDom = result.memory().domDom();
|
|
data->mMemoryInfo.mDomStyle = result.memory().domStyle();
|
|
data->mMemoryInfo.mDomOther = result.memory().domOther();
|
|
data->mMemoryInfo.mGCHeapUsage = result.memory().GCHeapUsage();
|
|
data->mMemoryInfo.mMedia.mAudioSize = result.memory().media().audioSize();
|
|
data->mMemoryInfo.mMedia.mVideoSize = result.memory().media().videoSize();
|
|
data->mMemoryInfo.mMedia.mResourcesSize =
|
|
result.memory().media().resourcesSize();
|
|
data->mItems = items;
|
|
}
|
|
|
|
mPendingResults--;
|
|
if (mPendingResults) {
|
|
return;
|
|
}
|
|
|
|
LOG(("[%s] All data collected, resolving promise",
|
|
nsIDToCString(mUUID).get()));
|
|
if (mIPCTimeout) {
|
|
mIPCTimeout->Cancel();
|
|
mIPCTimeout = nullptr;
|
|
}
|
|
mHolder.Resolve(mData, __func__);
|
|
mCollector->ForgetAggregatedResults(mUUID);
|
|
}
|
|
|
|
void AggregatedResults::SetNumResultsRequired(uint32_t aNumResultsRequired) {
|
|
MOZ_ASSERT(!mPendingResults && aNumResultsRequired);
|
|
mPendingResults = aNumResultsRequired;
|
|
}
|
|
|
|
RefPtr<RequestMetricsPromise> AggregatedResults::GetPromise() {
|
|
return mHolder.Ensure(__func__);
|
|
}
|
|
|
|
//
|
|
// class PerformanceMetricsCollector (singleton)
|
|
//
|
|
|
|
// raw pointer for the singleton
|
|
PerformanceMetricsCollector* gInstance = nullptr;
|
|
|
|
PerformanceMetricsCollector::~PerformanceMetricsCollector() {
|
|
MOZ_ASSERT(gInstance == this);
|
|
gInstance = nullptr;
|
|
}
|
|
|
|
void PerformanceMetricsCollector::ForgetAggregatedResults(const nsID& aUUID) {
|
|
MOZ_ASSERT(gInstance);
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
|
// This Remove() call will trigger AggregatedResults DTOR and if its
|
|
// the last in the table, the DTOR of PerformanceMetricsCollector.
|
|
// That's why we need to make sure we hold a reference here before the call
|
|
RefPtr<PerformanceMetricsCollector> kungFuDeathGrip = this;
|
|
LOG(("[%s] Removing from the table", nsIDToCString(aUUID).get()));
|
|
mAggregatedResults.Remove(aUUID);
|
|
}
|
|
|
|
// static
|
|
RefPtr<RequestMetricsPromise> PerformanceMetricsCollector::RequestMetrics() {
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
|
RefPtr<PerformanceMetricsCollector> pmc = gInstance;
|
|
if (!pmc) {
|
|
pmc = new PerformanceMetricsCollector();
|
|
gInstance = pmc;
|
|
}
|
|
return pmc->RequestMetricsInternal();
|
|
}
|
|
|
|
RefPtr<RequestMetricsPromise>
|
|
PerformanceMetricsCollector::RequestMetricsInternal() {
|
|
// each request has its own UUID
|
|
nsID uuid;
|
|
nsresult rv = nsContentUtils::GenerateUUIDInPlace(uuid);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return RequestMetricsPromise::CreateAndReject(rv, __func__);
|
|
}
|
|
|
|
LOG(("[%s] Requesting Performance Metrics", nsIDToCString(uuid).get()));
|
|
|
|
// Getting all content processes
|
|
nsTArray<ContentParent*> children;
|
|
ContentParent::GetAll(children);
|
|
uint32_t numChildren = children.Length();
|
|
|
|
// keep track of all results in an AggregatedResults instance
|
|
UniquePtr<AggregatedResults> results =
|
|
MakeUnique<AggregatedResults>(uuid, this);
|
|
RefPtr<RequestMetricsPromise> promise = results->GetPromise();
|
|
|
|
// We want to get back as many results as children + one parent if needed
|
|
uint32_t numResultsRequired = children.Length();
|
|
nsTArray<RefPtr<PerformanceInfoPromise>> localPromises =
|
|
CollectPerformanceInfo();
|
|
if (!localPromises.IsEmpty()) {
|
|
numResultsRequired++;
|
|
}
|
|
|
|
LOG(("[%s] Expecting %d results back", nsIDToCString(uuid).get(),
|
|
numResultsRequired));
|
|
results->SetNumResultsRequired(numResultsRequired);
|
|
mAggregatedResults.Put(uuid, std::move(results));
|
|
|
|
// calling all content processes via IPDL (async)
|
|
for (uint32_t i = 0; i < numChildren; i++) {
|
|
if (NS_WARN_IF(!children[i]->SendRequestPerformanceMetrics(uuid))) {
|
|
LOG(("[%s] Failed to send request to child %d", nsIDToCString(uuid).get(),
|
|
i));
|
|
mAggregatedResults.GetValue(uuid)->get()->Abort(NS_ERROR_FAILURE);
|
|
return RequestMetricsPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
|
}
|
|
LOG(("[%s] Request sent to child %d", nsIDToCString(uuid).get(), i));
|
|
}
|
|
|
|
nsTArray<RefPtr<PerformanceInfoPromise>> promises = CollectPerformanceInfo();
|
|
if (promises.IsEmpty()) {
|
|
return promise;
|
|
}
|
|
|
|
// collecting the current process PerformanceInfo
|
|
PerformanceInfoPromise::All(NS_GetCurrentThread(), localPromises)
|
|
->Then(
|
|
NS_GetCurrentThread(), __func__,
|
|
[uuid](const nsTArray<mozilla::dom::PerformanceInfo> aResult) {
|
|
LOG(("[%s] Local CollectPerformanceInfo promise resolved",
|
|
nsIDToCString(uuid).get()));
|
|
DataReceived(uuid, aResult);
|
|
},
|
|
[](const nsresult aResult) {});
|
|
|
|
return promise;
|
|
}
|
|
|
|
// static
|
|
nsresult PerformanceMetricsCollector::DataReceived(
|
|
const nsID& aUUID, const nsTArray<PerformanceInfo>& aMetrics) {
|
|
// If some content process were unresponsive on shutdown, we may get called
|
|
// here with late data received from children - so instead of asserting
|
|
// that gInstance is available, we just return.
|
|
if (!gInstance) {
|
|
LOG(("[%s] gInstance is gone", nsIDToCString(aUUID).get()));
|
|
return NS_OK;
|
|
}
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
|
return gInstance->DataReceivedInternal(aUUID, aMetrics);
|
|
}
|
|
|
|
nsresult PerformanceMetricsCollector::DataReceivedInternal(
|
|
const nsID& aUUID, const nsTArray<PerformanceInfo>& aMetrics) {
|
|
MOZ_ASSERT(gInstance == this);
|
|
UniquePtr<AggregatedResults>* results = mAggregatedResults.GetValue(aUUID);
|
|
if (!results) {
|
|
LOG(("[%s] UUID is gone from mAggregatedResults",
|
|
nsIDToCString(aUUID).get()));
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
LOG(("[%s] Received one PerformanceInfo array", nsIDToCString(aUUID).get()));
|
|
AggregatedResults* aggregatedResults = results->get();
|
|
MOZ_ASSERT(aggregatedResults);
|
|
|
|
// If this is the last result, AppendResult() will trigger the deletion
|
|
// of this collector, nothing should be done after this line.
|
|
aggregatedResults->AppendResult(aMetrics);
|
|
return NS_OK;
|
|
}
|
|
|
|
} // namespace mozilla
|