forked from mirrors/gecko-dev
		
	GetValue is going to be removed in a subsequent patch. It is no longer needed, because it can be replaced by functions already provided by nsBaseHashtable, in particular Lookup and Contains. Also, its name was confusing, since it specifically returns a pointer that allows and is intended for modifying the entry within the hashtable, rather than returning by-value. According to the naming rules to be set on nsBaseHashtable, it would also needed to be renamed to "Lookup*. Removing its uses saves this effort. Differential Revision: https://phabricator.services.mozilla.com/D105476
		
			
				
	
	
		
			312 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			312 lines
		
	
	
	
		
			10 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(CopyableTArray(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;
 | 
						|
  }
 | 
						|
  nsTArray<dom::PerformanceInfoDictionary> data;
 | 
						|
  data.Assign(mData);
 | 
						|
  mHolder.Resolve(std::move(data), __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);
 | 
						|
  const auto& aggregatedResult =
 | 
						|
      mAggregatedResults.InsertOrUpdate(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));
 | 
						|
      aggregatedResult->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);
 | 
						|
  auto results = mAggregatedResults.Lookup(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
 |