forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			1558 lines
		
	
	
	
		
			42 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1558 lines
		
	
	
	
		
			42 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; 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 "nsPerformanceStats.h"
 | |
| 
 | |
| #include "nsMemory.h"
 | |
| #include "nsLiteralString.h"
 | |
| #include "nsCRTGlue.h"
 | |
| #include "nsServiceManagerUtils.h"
 | |
| 
 | |
| #include "nsCOMArray.h"
 | |
| #include "nsContentUtils.h"
 | |
| #include "nsIMutableArray.h"
 | |
| #include "nsReadableUtils.h"
 | |
| 
 | |
| #include "jsapi.h"
 | |
| #include "nsJSUtils.h"
 | |
| #include "xpcpublic.h"
 | |
| #include "jspubtd.h"
 | |
| 
 | |
| #include "nsIDOMWindow.h"
 | |
| #include "nsGlobalWindow.h"
 | |
| #include "nsRefreshDriver.h"
 | |
| #include "nsThreadUtils.h"
 | |
| 
 | |
| #include "mozilla/Unused.h"
 | |
| #include "mozilla/ArrayUtils.h"
 | |
| #include "mozilla/dom/ScriptSettings.h"
 | |
| #include "mozilla/EventStateManager.h"
 | |
| #include "mozilla/Services.h"
 | |
| #include "mozilla/Telemetry.h"
 | |
| 
 | |
| #if defined(XP_WIN)
 | |
| #include <processthreadsapi.h>
 | |
| #include <windows.h>
 | |
| #else
 | |
| #include <unistd.h>
 | |
| #endif // defined(XP_WIN)
 | |
| 
 | |
| #if defined(XP_MACOSX)
 | |
| #include <mach/mach_init.h>
 | |
| #include <mach/mach_interface.h>
 | |
| #include <mach/mach_port.h>
 | |
| #include <mach/mach_types.h>
 | |
| #include <mach/message.h>
 | |
| #include <mach/thread_info.h>
 | |
| #elif defined(XP_UNIX)
 | |
| #include <sys/time.h>
 | |
| #include <sys/resource.h>
 | |
| #endif // defined(XP_UNIX)
 | |
| /* ------------------------------------------------------
 | |
|  *
 | |
|  * Utility functions.
 | |
|  *
 | |
|  */
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| /**
 | |
|  * Get the private window for the current compartment.
 | |
|  *
 | |
|  * @return null if the code is not executed in a window or in
 | |
|  * case of error, a nsPIDOMWindow otherwise.
 | |
|  */
 | |
| already_AddRefed<nsPIDOMWindowOuter>
 | |
| GetPrivateWindow(JSContext* cx) {
 | |
|   nsGlobalWindowInner* win = xpc::CurrentWindowOrNull(cx);
 | |
|   if (!win) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   nsPIDOMWindowOuter* outer = win->AsInner()->GetOuterWindow();
 | |
|   if (!outer) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsPIDOMWindowOuter> top = outer->GetTop();
 | |
|   if (!top) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   return top.forget();
 | |
| }
 | |
| 
 | |
| bool
 | |
| URLForGlobal(JSContext* cx, JS::Handle<JSObject*> global, nsAString& url) {
 | |
|   nsCOMPtr<nsIPrincipal> principal = nsContentUtils::ObjectPrincipal(global);
 | |
|   if (!principal) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIURI> uri;
 | |
|   nsresult rv = principal->GetURI(getter_AddRefs(uri));
 | |
|   if (NS_FAILED(rv) || !uri) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   nsAutoCString spec;
 | |
|   rv = uri->GetSpec(spec);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   url.Assign(NS_ConvertUTF8toUTF16(spec));
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Extract a somewhat human-readable name from the current context.
 | |
|  */
 | |
| void
 | |
| RealmName(JSContext* cx, JS::Handle<JSObject*> global, nsAString& name) {
 | |
|   // Attempt to use the URL as name.
 | |
|   if (URLForGlobal(cx, global, name)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Otherwise, fallback to XPConnect's less readable but more
 | |
|   // complete naming scheme.
 | |
|   nsAutoCString cname;
 | |
|   xpc::GetCurrentRealmName(cx, cname);
 | |
|   name.Assign(NS_ConvertUTF8toUTF16(cname));
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Generate a unique-to-the-application identifier for a group.
 | |
|  */
 | |
| void
 | |
| GenerateUniqueGroupId(uint64_t uid, uint64_t processId, nsAString& groupId)
 | |
| {
 | |
|   uint64_t threadId = reinterpret_cast<uint64_t>(mozilla::GetCurrentPhysicalThread());
 | |
| 
 | |
|   groupId.AssignLiteral("process: ");
 | |
|   groupId.AppendInt(processId);
 | |
|   groupId.AppendLiteral(", thread: ");
 | |
|   groupId.AppendInt(threadId);
 | |
|   groupId.AppendLiteral(", group: ");
 | |
|   groupId.AppendInt(uid);
 | |
| }
 | |
| 
 | |
| static const char* TOPICS[] = {
 | |
|   "profile-before-change",
 | |
|   "quit-application",
 | |
|   "quit-application-granted",
 | |
|   "xpcom-will-shutdown"
 | |
| };
 | |
| 
 | |
| } // namespace
 | |
| 
 | |
| /* ------------------------------------------------------
 | |
|  *
 | |
|  * class nsPerformanceObservationTarget
 | |
|  *
 | |
|  */
 | |
| 
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(nsPerformanceObservationTarget, nsIPerformanceObservable)
 | |
| 
 | |
| 
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsPerformanceObservationTarget::GetTarget(nsIPerformanceGroupDetails** _result) {
 | |
|   if (mDetails) {
 | |
|     NS_IF_ADDREF(*_result = mDetails);
 | |
|   }
 | |
|   return NS_OK;
 | |
| };
 | |
| 
 | |
| void
 | |
| nsPerformanceObservationTarget::SetTarget(nsPerformanceGroupDetails* details) {
 | |
|   MOZ_ASSERT(!mDetails);
 | |
|   mDetails = details;
 | |
| };
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsPerformanceObservationTarget::AddJankObserver(nsIPerformanceObserver* observer) {
 | |
|   if (!mObservers.append(observer)) {
 | |
|     MOZ_CRASH();
 | |
|   }
 | |
|   return NS_OK;
 | |
| };
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsPerformanceObservationTarget::RemoveJankObserver(nsIPerformanceObserver* observer) {
 | |
|   for (auto iter = mObservers.begin(), end = mObservers.end(); iter < end; ++iter) {
 | |
|     if (*iter == observer) {
 | |
|       mObservers.erase(iter);
 | |
|       return NS_OK;
 | |
|     }
 | |
|   }
 | |
|   return NS_OK;
 | |
| };
 | |
| 
 | |
| bool
 | |
| nsPerformanceObservationTarget::HasObservers() const {
 | |
|   return !mObservers.empty();
 | |
| }
 | |
| 
 | |
| void
 | |
| nsPerformanceObservationTarget::NotifyJankObservers(nsIPerformanceGroupDetails* source, nsIPerformanceAlert* gravity) {
 | |
|   // Copy the vector to make sure that it won't change under our feet.
 | |
|   mozilla::Vector<nsCOMPtr<nsIPerformanceObserver>> observers;
 | |
|   if (!observers.appendAll(mObservers)) {
 | |
|     MOZ_CRASH();
 | |
|   }
 | |
| 
 | |
|   // Now actually notify.
 | |
|   for (auto iter = observers.begin(), end = observers.end(); iter < end; ++iter) {
 | |
|     nsCOMPtr<nsIPerformanceObserver> observer = *iter;
 | |
|     mozilla::Unused << observer->Observe(source, gravity);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* ------------------------------------------------------
 | |
|  *
 | |
|  * class nsGroupHolder
 | |
|  *
 | |
|  */
 | |
| 
 | |
| nsPerformanceObservationTarget*
 | |
| nsGroupHolder::ObservationTarget() {
 | |
|   if (!mPendingObservationTarget) {
 | |
|     mPendingObservationTarget = new nsPerformanceObservationTarget();
 | |
|   }
 | |
|   return mPendingObservationTarget;
 | |
| }
 | |
| 
 | |
| nsPerformanceGroup*
 | |
| nsGroupHolder::GetGroup() {
 | |
|   return mGroup;
 | |
| }
 | |
| 
 | |
| void
 | |
| nsGroupHolder::SetGroup(nsPerformanceGroup* group) {
 | |
|   MOZ_ASSERT(!mGroup);
 | |
|   mGroup = group;
 | |
|   group->SetObservationTarget(ObservationTarget());
 | |
|   mPendingObservationTarget->SetTarget(group->Details());
 | |
| }
 | |
| 
 | |
| /* ------------------------------------------------------
 | |
|  *
 | |
|  * struct PerformanceData
 | |
|  *
 | |
|  */
 | |
| 
 | |
| PerformanceData::PerformanceData()
 | |
|   : mTotalUserTime(0)
 | |
|   , mTotalSystemTime(0)
 | |
|   , mTotalCPOWTime(0)
 | |
|   , mTicks(0)
 | |
| {
 | |
|   mozilla::PodArrayZero(mDurations);
 | |
| }
 | |
| 
 | |
| /* ------------------------------------------------------
 | |
|  *
 | |
|  * class nsPerformanceGroupDetails
 | |
|  *
 | |
|  */
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(nsPerformanceGroupDetails, nsIPerformanceGroupDetails)
 | |
| 
 | |
| const nsAString&
 | |
| nsPerformanceGroupDetails::Name() const {
 | |
|   return mName;
 | |
| }
 | |
| 
 | |
| const nsAString&
 | |
| nsPerformanceGroupDetails::GroupId() const {
 | |
|   return mGroupId;
 | |
| }
 | |
| 
 | |
| uint64_t
 | |
| nsPerformanceGroupDetails::WindowId() const {
 | |
|   return mWindowId;
 | |
| }
 | |
| 
 | |
| uint64_t
 | |
| nsPerformanceGroupDetails::ProcessId() const {
 | |
|   return mProcessId;
 | |
| }
 | |
| 
 | |
| bool
 | |
| nsPerformanceGroupDetails::IsSystem() const {
 | |
|   return mIsSystem;
 | |
| }
 | |
| 
 | |
| bool
 | |
| nsPerformanceGroupDetails::IsWindow() const {
 | |
|   return mWindowId != 0;
 | |
| }
 | |
| 
 | |
| bool
 | |
| nsPerformanceGroupDetails::IsContentProcess() const {
 | |
|   return XRE_GetProcessType() == GeckoProcessType_Content;
 | |
| }
 | |
| 
 | |
| /* readonly attribute AString name; */
 | |
| NS_IMETHODIMP
 | |
| nsPerformanceGroupDetails::GetName(nsAString& aName) {
 | |
|   aName.Assign(Name());
 | |
|   return NS_OK;
 | |
| };
 | |
| 
 | |
| /* readonly attribute AString groupId; */
 | |
| NS_IMETHODIMP
 | |
| nsPerformanceGroupDetails::GetGroupId(nsAString& aGroupId) {
 | |
|   aGroupId.Assign(GroupId());
 | |
|   return NS_OK;
 | |
| };
 | |
| 
 | |
| /* readonly attribute uint64_t windowId; */
 | |
| NS_IMETHODIMP
 | |
| nsPerformanceGroupDetails::GetWindowId(uint64_t *aWindowId) {
 | |
|   *aWindowId = WindowId();
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| /* readonly attribute bool isSystem; */
 | |
| NS_IMETHODIMP
 | |
| nsPerformanceGroupDetails::GetIsSystem(bool *_retval) {
 | |
|   *_retval = IsSystem();
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| /*
 | |
|   readonly attribute unsigned long long processId;
 | |
| */
 | |
| NS_IMETHODIMP
 | |
| nsPerformanceGroupDetails::GetProcessId(uint64_t* processId) {
 | |
|   *processId = ProcessId();
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| /* readonly attribute bool IsContentProcess; */
 | |
| NS_IMETHODIMP
 | |
| nsPerformanceGroupDetails::GetIsContentProcess(bool *_retval) {
 | |
|   *_retval = IsContentProcess();
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| 
 | |
| /* ------------------------------------------------------
 | |
|  *
 | |
|  * class nsPerformanceStats
 | |
|  *
 | |
|  */
 | |
| 
 | |
| class nsPerformanceStats final: public nsIPerformanceStats
 | |
| {
 | |
| public:
 | |
|   NS_DECL_ISUPPORTS
 | |
|   NS_DECL_NSIPERFORMANCESTATS
 | |
|   NS_FORWARD_NSIPERFORMANCEGROUPDETAILS(mDetails->)
 | |
| 
 | |
|   nsPerformanceStats(nsPerformanceGroupDetails* item,
 | |
|                      const PerformanceData& aPerformanceData)
 | |
|     : mDetails(item)
 | |
|     , mPerformanceData(aPerformanceData)
 | |
|   {
 | |
|   }
 | |
| 
 | |
| 
 | |
| private:
 | |
|   RefPtr<nsPerformanceGroupDetails> mDetails;
 | |
|   PerformanceData mPerformanceData;
 | |
| 
 | |
|   ~nsPerformanceStats() {}
 | |
| };
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(nsPerformanceStats, nsIPerformanceStats, nsIPerformanceGroupDetails)
 | |
| 
 | |
| /* readonly attribute unsigned long long totalUserTime; */
 | |
| NS_IMETHODIMP
 | |
| nsPerformanceStats::GetTotalUserTime(uint64_t *aTotalUserTime) {
 | |
|   *aTotalUserTime = mPerformanceData.mTotalUserTime;
 | |
|   return NS_OK;
 | |
| };
 | |
| 
 | |
| /* readonly attribute unsigned long long totalSystemTime; */
 | |
| NS_IMETHODIMP
 | |
| nsPerformanceStats::GetTotalSystemTime(uint64_t *aTotalSystemTime) {
 | |
|   *aTotalSystemTime = mPerformanceData.mTotalSystemTime;
 | |
|   return NS_OK;
 | |
| };
 | |
| 
 | |
| /* readonly attribute unsigned long long totalCPOWTime; */
 | |
| NS_IMETHODIMP
 | |
| nsPerformanceStats::GetTotalCPOWTime(uint64_t *aCpowTime) {
 | |
|   *aCpowTime = mPerformanceData.mTotalCPOWTime;
 | |
|   return NS_OK;
 | |
| };
 | |
| 
 | |
| /* readonly attribute unsigned long long ticks; */
 | |
| NS_IMETHODIMP
 | |
| nsPerformanceStats::GetTicks(uint64_t *aTicks) {
 | |
|   *aTicks = mPerformanceData.mTicks;
 | |
|   return NS_OK;
 | |
| };
 | |
| 
 | |
| /* void getDurations (out unsigned long aCount, [array, size_is (aCount), retval] out unsigned long long aNumberOfOccurrences); */
 | |
| NS_IMETHODIMP
 | |
| nsPerformanceStats::GetDurations(uint32_t *aCount, uint64_t **aNumberOfOccurrences) {
 | |
|   const size_t length = mozilla::ArrayLength(mPerformanceData.mDurations);
 | |
|   if (aCount) {
 | |
|     *aCount = length;
 | |
|   }
 | |
|   *aNumberOfOccurrences = new uint64_t[length];
 | |
|   for (size_t i = 0; i < length; ++i) {
 | |
|     (*aNumberOfOccurrences)[i] = mPerformanceData.mDurations[i];
 | |
|   }
 | |
|   return NS_OK;
 | |
| };
 | |
| 
 | |
| 
 | |
| /* ------------------------------------------------------
 | |
|  *
 | |
|  * struct nsPerformanceSnapshot
 | |
|  *
 | |
|  */
 | |
| 
 | |
| class nsPerformanceSnapshot final : public nsIPerformanceSnapshot
 | |
| {
 | |
| public:
 | |
|   NS_DECL_ISUPPORTS
 | |
|   NS_DECL_NSIPERFORMANCESNAPSHOT
 | |
| 
 | |
|   nsPerformanceSnapshot() {}
 | |
| 
 | |
|   /**
 | |
|    * Append statistics to the list of components data.
 | |
|    */
 | |
|   void AppendComponentsStats(nsIPerformanceStats* stats);
 | |
| 
 | |
|   /**
 | |
|    * Set the statistics attached to process data.
 | |
|    */
 | |
|   void SetProcessStats(nsIPerformanceStats* group);
 | |
| 
 | |
| private:
 | |
|   ~nsPerformanceSnapshot() {}
 | |
| 
 | |
| private:
 | |
|   /**
 | |
|    * The data for all components.
 | |
|    */
 | |
|   nsCOMArray<nsIPerformanceStats> mComponentsData;
 | |
| 
 | |
|   /**
 | |
|    * The data for the process.
 | |
|    */
 | |
|   nsCOMPtr<nsIPerformanceStats> mProcessData;
 | |
| };
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(nsPerformanceSnapshot, nsIPerformanceSnapshot)
 | |
| 
 | |
| 
 | |
| /* nsIArray getComponentsData (); */
 | |
| NS_IMETHODIMP
 | |
| nsPerformanceSnapshot::GetComponentsData(nsIArray * *aComponents)
 | |
| {
 | |
|   const size_t length = mComponentsData.Length();
 | |
|   nsCOMPtr<nsIMutableArray> components = do_CreateInstance(NS_ARRAY_CONTRACTID);
 | |
|   for (size_t i = 0; i < length; ++i) {
 | |
|     nsCOMPtr<nsIPerformanceStats> stats = mComponentsData[i];
 | |
|     mozilla::DebugOnly<nsresult> rv = components->AppendElement(stats);
 | |
|     MOZ_ASSERT(NS_SUCCEEDED(rv));
 | |
|   }
 | |
|   components.forget(aComponents);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| /* nsIPerformanceStats getProcessData (); */
 | |
| NS_IMETHODIMP
 | |
| nsPerformanceSnapshot::GetProcessData(nsIPerformanceStats * *aProcess)
 | |
| {
 | |
|   NS_IF_ADDREF(*aProcess = mProcessData);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void
 | |
| nsPerformanceSnapshot::AppendComponentsStats(nsIPerformanceStats* stats)
 | |
| {
 | |
|   mComponentsData.AppendElement(stats);
 | |
| }
 | |
| 
 | |
| void
 | |
| nsPerformanceSnapshot::SetProcessStats(nsIPerformanceStats* stats)
 | |
| {
 | |
|   mProcessData = stats;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /* ------------------------------------------------------
 | |
|  *
 | |
|  * class PerformanceAlert
 | |
|  *
 | |
|  */
 | |
| class PerformanceAlert final: public nsIPerformanceAlert {
 | |
| public:
 | |
|   NS_DECL_ISUPPORTS
 | |
|   NS_DECL_NSIPERFORMANCEALERT
 | |
| 
 | |
|   PerformanceAlert(const uint32_t reason, nsPerformanceGroup* source);
 | |
| private:
 | |
|   ~PerformanceAlert() {}
 | |
| 
 | |
|   const uint32_t mReason;
 | |
| 
 | |
|   // The highest values reached by this group since the latest alert,
 | |
|   // in microseconds.
 | |
|   const uint64_t mHighestJank;
 | |
|   const uint64_t mHighestCPOW;
 | |
| };
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(PerformanceAlert, nsIPerformanceAlert);
 | |
| 
 | |
| PerformanceAlert::PerformanceAlert(const uint32_t reason, nsPerformanceGroup* source)
 | |
|   : mReason(reason)
 | |
|   , mHighestJank(source->HighestRecentJank())
 | |
|   , mHighestCPOW(source->HighestRecentCPOW())
 | |
| { }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| PerformanceAlert::GetHighestJank(uint64_t* result) {
 | |
|   *result = mHighestJank;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| PerformanceAlert::GetHighestCPOW(uint64_t* result) {
 | |
|   *result = mHighestCPOW;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| PerformanceAlert::GetReason(uint32_t* result) {
 | |
|   *result = mReason;
 | |
|   return NS_OK;
 | |
| }
 | |
| /* ------------------------------------------------------
 | |
|  *
 | |
|  * class PendingAlertsCollector
 | |
|  *
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * A timer callback in charge of collecting the groups in
 | |
|  * `mPendingAlerts` and triggering dispatch of performance alerts.
 | |
|  */
 | |
| class PendingAlertsCollector final :
 | |
|   public nsITimerCallback,
 | |
|   public nsINamed
 | |
| {
 | |
| public:
 | |
|   NS_DECL_ISUPPORTS
 | |
|   NS_DECL_NSITIMERCALLBACK
 | |
|   NS_DECL_NSINAMED
 | |
| 
 | |
|   explicit PendingAlertsCollector(nsPerformanceStatsService* service)
 | |
|     : mService(service)
 | |
|     , mPending(false)
 | |
|   { }
 | |
| 
 | |
|   nsresult Start(uint32_t timerDelayMS);
 | |
|   nsresult Dispose();
 | |
| 
 | |
| private:
 | |
|   ~PendingAlertsCollector() {}
 | |
| 
 | |
|   RefPtr<nsPerformanceStatsService> mService;
 | |
|   bool mPending;
 | |
| 
 | |
|   nsCOMPtr<nsITimer> mTimer;
 | |
| 
 | |
|   mozilla::Vector<uint64_t> mJankLevels;
 | |
| };
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(PendingAlertsCollector, nsITimerCallback, nsINamed);
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| PendingAlertsCollector::Notify(nsITimer*) {
 | |
|   mPending = false;
 | |
|   mService->NotifyJankObservers(mJankLevels);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| PendingAlertsCollector::GetName(nsACString& aName)
 | |
| {
 | |
|   aName.AssignASCII("PendingAlertsCollector_timer");
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult
 | |
| PendingAlertsCollector::Start(uint32_t timerDelayMS) {
 | |
|   if (mPending) {
 | |
|     // Collector is already started.
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   if (!mTimer) {
 | |
|     mTimer = NS_NewTimer();
 | |
|   }
 | |
| 
 | |
|   nsresult rv = mTimer->InitWithCallback(this, timerDelayMS, nsITimer::TYPE_ONE_SHOT);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   mPending = true;
 | |
|   {
 | |
|     mozilla::DebugOnly<bool> result = nsRefreshDriver::GetJankLevels(mJankLevels);
 | |
|     MOZ_ASSERT(result);
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult
 | |
| PendingAlertsCollector::Dispose() {
 | |
|   if (mTimer) {
 | |
|     mozilla::Unused << mTimer->Cancel();
 | |
|     mTimer = nullptr;
 | |
|   }
 | |
|   mService = nullptr;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /* ------------------------------------------------------
 | |
|  *
 | |
|  * class nsPerformanceStatsService
 | |
|  *
 | |
|  */
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(nsPerformanceStatsService, nsIPerformanceStatsService, nsIObserver)
 | |
| 
 | |
| nsPerformanceStatsService::nsPerformanceStatsService()
 | |
|   : mIsAvailable(false)
 | |
|   , mDisposed(false)
 | |
| #if defined(XP_WIN)
 | |
|   , mProcessId(GetCurrentProcessId())
 | |
| #else
 | |
|   , mProcessId(getpid())
 | |
| #endif
 | |
|   , mUIdCounter(0)
 | |
|   , mTopGroup(nsPerformanceGroup::Make(this,
 | |
|                                        NS_LITERAL_STRING("<process>"), // name
 | |
|                                        0,    // windowId
 | |
|                                        mProcessId,
 | |
|                                        true, // isSystem
 | |
|                                        nsPerformanceGroup::GroupScope::RUNTIME // scope
 | |
|                                      ))
 | |
|   , mIsHandlingUserInput(false)
 | |
|   , mProcessStayed(0)
 | |
|   , mProcessMoved(0)
 | |
|   , mProcessUpdateCounter(0)
 | |
|   , mIsMonitoringPerCompartment(false)
 | |
|   , mJankAlertThreshold(mozilla::MaxValue<uint64_t>::value) // By default, no alerts
 | |
|   , mJankAlertBufferingDelay(1000 /* ms */)
 | |
|   , mJankLevelVisibilityThreshold(/* 2 ^ */ 8 /* ms */)
 | |
|   , mMaxExpectedDurationOfInteractionUS(150 * 1000)
 | |
| {
 | |
|   mPendingAlertsCollector = new PendingAlertsCollector(this);
 | |
| 
 | |
|   nsString groupIdForWindows;
 | |
|   GenerateUniqueGroupId(GetNextId(), mProcessId, groupIdForWindows);
 | |
|   mUniversalTargets.mWindows->
 | |
|     SetTarget(new nsPerformanceGroupDetails(NS_LITERAL_STRING("<universal window listener>"),
 | |
|                                             groupIdForWindows,
 | |
|                                             0, // window id
 | |
|                                             mProcessId,
 | |
|                                             false));
 | |
| }
 | |
| 
 | |
| nsPerformanceStatsService::~nsPerformanceStatsService()
 | |
| { }
 | |
| 
 | |
| /**
 | |
|  * Clean up the service.
 | |
|  *
 | |
|  * Called during shutdown. Idempotent.
 | |
|  */
 | |
| void
 | |
| nsPerformanceStatsService::Dispose()
 | |
| {
 | |
|   // Make sure that we do not accidentally destroy `this` while we are
 | |
|   // cleaning up back references.
 | |
|   RefPtr<nsPerformanceStatsService> kungFuDeathGrip(this);
 | |
|   mIsAvailable = false;
 | |
| 
 | |
|   if (mDisposed) {
 | |
|     // Make sure that we don't double-dispose.
 | |
|     return;
 | |
|   }
 | |
|   mDisposed = true;
 | |
| 
 | |
|   // Disconnect from nsIObserverService.
 | |
|   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
 | |
|   if (obs) {
 | |
|     for (size_t i = 0; i < mozilla::ArrayLength(TOPICS); ++i) {
 | |
|       mozilla::Unused << obs->RemoveObserver(this, TOPICS[i]);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Clear up and disconnect from JSAPI.
 | |
|   mozilla::dom::AutoJSAPI jsapi;
 | |
|   jsapi.Init();
 | |
|   JSContext* cx = jsapi.cx();
 | |
|   js::DisposePerformanceMonitoring(cx);
 | |
| 
 | |
|   mozilla::Unused << js::SetStopwatchIsMonitoringCPOW(cx, false);
 | |
|   mozilla::Unused << js::SetStopwatchIsMonitoringJank(cx, false);
 | |
| 
 | |
|   mozilla::Unused << js::SetStopwatchStartCallback(cx, nullptr, nullptr);
 | |
|   mozilla::Unused << js::SetStopwatchCommitCallback(cx, nullptr, nullptr);
 | |
|   mozilla::Unused << js::SetGetPerformanceGroupsCallback(cx, nullptr, nullptr);
 | |
| 
 | |
|   // Clear up and disconnect the alerts collector.
 | |
|   if (mPendingAlertsCollector) {
 | |
|     mPendingAlertsCollector->Dispose();
 | |
|     mPendingAlertsCollector = nullptr;
 | |
|   }
 | |
|   mPendingAlerts.clear();
 | |
| 
 | |
|   // Disconnect universal observers. Per-group observers will be
 | |
|   // disconnected below as part of `group->Dispose()`.
 | |
|   mUniversalTargets.mWindows = nullptr;
 | |
| 
 | |
|   // At this stage, the JS VM may still be holding references to
 | |
|   // instances of PerformanceGroup on the stack. To let the service be
 | |
|   // collected, we need to break the references from these groups to
 | |
|   // `this`.
 | |
|   mTopGroup->Dispose();
 | |
|   mTopGroup = nullptr;
 | |
| 
 | |
|   // Copy references to the groups to a vector to ensure that we do
 | |
|   // not modify the hashtable while iterating it.
 | |
|   GroupVector groups;
 | |
|   for (auto iter = mGroups.Iter(); !iter.Done(); iter.Next()) {
 | |
|     if (!groups.append(iter.Get()->GetKey())) {
 | |
|       MOZ_CRASH();
 | |
|     }
 | |
|   }
 | |
|   for (auto iter = groups.begin(), end = groups.end(); iter < end; ++iter) {
 | |
|     RefPtr<nsPerformanceGroup> group = *iter;
 | |
|     group->Dispose();
 | |
|   }
 | |
| 
 | |
|   // Any remaining references to PerformanceGroup will be released as
 | |
|   // the VM unrolls the stack. If there are any nested event loops,
 | |
|   // this may take time.
 | |
| }
 | |
| 
 | |
| nsresult
 | |
| nsPerformanceStatsService::Init()
 | |
| {
 | |
|   nsresult rv = InitInternal();
 | |
|   if (NS_FAILED(rv)) {
 | |
|     // Attempt to clean up.
 | |
|     Dispose();
 | |
|   }
 | |
|   return rv;
 | |
| }
 | |
| 
 | |
| nsresult
 | |
| nsPerformanceStatsService::InitInternal()
 | |
| {
 | |
|   // Make sure that we release everything during shutdown.
 | |
|   // We are a bit defensive here, as we know that some strange behavior can break the
 | |
|   // regular shutdown order.
 | |
|   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
 | |
|   if (obs) {
 | |
|     for (size_t i = 0; i < mozilla::ArrayLength(TOPICS); ++i) {
 | |
|       mozilla::Unused << obs->AddObserver(this, TOPICS[i], false);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Connect to JSAPI.
 | |
|   mozilla::dom::AutoJSAPI jsapi;
 | |
|   jsapi.Init();
 | |
|   JSContext* cx = jsapi.cx();
 | |
|   if (!js::SetStopwatchStartCallback(cx, StopwatchStartCallback, this)) {
 | |
|     return NS_ERROR_UNEXPECTED;
 | |
|   }
 | |
|   if (!js::SetStopwatchCommitCallback(cx, StopwatchCommitCallback, this)) {
 | |
|     return NS_ERROR_UNEXPECTED;
 | |
|   }
 | |
|   if (!js::SetGetPerformanceGroupsCallback(cx, GetPerformanceGroupsCallback, this)) {
 | |
|     return NS_ERROR_UNEXPECTED;
 | |
|   }
 | |
| 
 | |
|   mTopGroup->setIsActive(true);
 | |
|   mIsAvailable = true;
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| // Observe shutdown events.
 | |
| NS_IMETHODIMP
 | |
| nsPerformanceStatsService::Observe(nsISupports *aSubject, const char *aTopic,
 | |
|                                    const char16_t *aData)
 | |
| {
 | |
|   MOZ_ASSERT(strcmp(aTopic, "profile-before-change") == 0
 | |
|              || strcmp(aTopic, "quit-application") == 0
 | |
|              || strcmp(aTopic, "quit-application-granted") == 0
 | |
|              || strcmp(aTopic, "xpcom-will-shutdown") == 0);
 | |
| 
 | |
|   Dispose();
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| /*static*/ bool
 | |
| nsPerformanceStatsService::IsHandlingUserInput() {
 | |
|   if (mozilla::EventStateManager::LatestUserInputStart().IsNull()) {
 | |
|     return false;
 | |
|   }
 | |
|   bool result = mozilla::TimeStamp::Now() - mozilla::EventStateManager::LatestUserInputStart() <= mozilla::TimeDuration::FromMicroseconds(mMaxExpectedDurationOfInteractionUS);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /* [implicit_jscontext] attribute bool isMonitoringCPOW; */
 | |
| NS_IMETHODIMP
 | |
| nsPerformanceStatsService::GetIsMonitoringCPOW(JSContext* cx, bool *aIsStopwatchActive)
 | |
| {
 | |
|   if (!mIsAvailable) {
 | |
|     return NS_ERROR_NOT_AVAILABLE;
 | |
|   }
 | |
| 
 | |
|   *aIsStopwatchActive = js::GetStopwatchIsMonitoringCPOW(cx);
 | |
|   return NS_OK;
 | |
| }
 | |
| NS_IMETHODIMP
 | |
| nsPerformanceStatsService::SetIsMonitoringCPOW(JSContext* cx, bool aIsStopwatchActive)
 | |
| {
 | |
|   if (!mIsAvailable) {
 | |
|     return NS_ERROR_NOT_AVAILABLE;
 | |
|   }
 | |
| 
 | |
|   if (!js::SetStopwatchIsMonitoringCPOW(cx, aIsStopwatchActive)) {
 | |
|     return NS_ERROR_OUT_OF_MEMORY;
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| /* [implicit_jscontext] attribute bool isMonitoringJank; */
 | |
| NS_IMETHODIMP
 | |
| nsPerformanceStatsService::GetIsMonitoringJank(JSContext* cx, bool *aIsStopwatchActive)
 | |
| {
 | |
|   if (!mIsAvailable) {
 | |
|     return NS_ERROR_NOT_AVAILABLE;
 | |
|   }
 | |
| 
 | |
|   *aIsStopwatchActive = js::GetStopwatchIsMonitoringJank(cx);
 | |
|   return NS_OK;
 | |
| }
 | |
| NS_IMETHODIMP
 | |
| nsPerformanceStatsService::SetIsMonitoringJank(JSContext* cx, bool aIsStopwatchActive)
 | |
| {
 | |
|   if (!mIsAvailable) {
 | |
|     return NS_ERROR_NOT_AVAILABLE;
 | |
|   }
 | |
| 
 | |
|   if (!js::SetStopwatchIsMonitoringJank(cx, aIsStopwatchActive)) {
 | |
|     return NS_ERROR_OUT_OF_MEMORY;
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| /* [implicit_jscontext] attribute bool isMonitoringPerCompartment; */
 | |
| NS_IMETHODIMP
 | |
| nsPerformanceStatsService::GetIsMonitoringPerCompartment(JSContext*, bool *aIsMonitoringPerCompartment)
 | |
| {
 | |
|   if (!mIsAvailable) {
 | |
|     return NS_ERROR_NOT_AVAILABLE;
 | |
|   }
 | |
| 
 | |
|   *aIsMonitoringPerCompartment = mIsMonitoringPerCompartment;
 | |
|   return NS_OK;
 | |
| }
 | |
| NS_IMETHODIMP
 | |
| nsPerformanceStatsService::SetIsMonitoringPerCompartment(JSContext*, bool aIsMonitoringPerCompartment)
 | |
| {
 | |
|   if (!mIsAvailable) {
 | |
|     return NS_ERROR_NOT_AVAILABLE;
 | |
|   }
 | |
| 
 | |
|   if (aIsMonitoringPerCompartment == mIsMonitoringPerCompartment) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // Relatively slow update: walk the entire lost of performance groups,
 | |
|   // update the active flag of those that have changed.
 | |
|   //
 | |
|   // Alternative strategies could be envisioned to make the update
 | |
|   // much faster, at the expense of the speed of calling `isActive()`,
 | |
|   // (e.g. deferring `isActive()` to the nsPerformanceStatsService),
 | |
|   // but we expect that `isActive()` can be called thousands of times
 | |
|   // per second, while `SetIsMonitoringPerCompartment` is not called
 | |
|   // at all during most Firefox runs.
 | |
| 
 | |
|   for (auto iter = mGroups.Iter(); !iter.Done(); iter.Next()) {
 | |
|     RefPtr<nsPerformanceGroup> group = iter.Get()->GetKey();
 | |
|     if (group->Scope() == nsPerformanceGroup::GroupScope::COMPARTMENT) {
 | |
|       group->setIsActive(aIsMonitoringPerCompartment);
 | |
|     }
 | |
|   }
 | |
|   mIsMonitoringPerCompartment = aIsMonitoringPerCompartment;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsPerformanceStatsService::GetJankAlertThreshold(uint64_t* result) {
 | |
|   *result = mJankAlertThreshold;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsPerformanceStatsService::SetJankAlertThreshold(uint64_t value) {
 | |
|   mJankAlertThreshold = value;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsPerformanceStatsService::GetJankAlertBufferingDelay(uint32_t* result) {
 | |
|   *result = mJankAlertBufferingDelay;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsPerformanceStatsService::SetJankAlertBufferingDelay(uint32_t value) {
 | |
|   mJankAlertBufferingDelay = value;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult
 | |
| nsPerformanceStatsService::UpdateTelemetry()
 | |
| {
 | |
|   // Promote everything to floating-point explicitly before dividing.
 | |
|   const double processStayed = mProcessStayed;
 | |
|   const double processMoved = mProcessMoved;
 | |
| 
 | |
|   if (processStayed <= 0 || processMoved <= 0 || processStayed + processMoved <= 0) {
 | |
|     // Overflow/underflow/nothing to report
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   const double proportion = (100 * processStayed) / (processStayed + processMoved);
 | |
|   if (proportion < 0 || proportion > 100) {
 | |
|     // Overflow/underflow
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   mozilla::Telemetry::Accumulate(mozilla::Telemetry::PERF_MONITORING_TEST_CPU_RESCHEDULING_PROPORTION_MOVED, (uint32_t)proportion);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| 
 | |
| /* static */ nsIPerformanceStats*
 | |
| nsPerformanceStatsService::GetStatsForGroup(const js::PerformanceGroup* group)
 | |
| {
 | |
|   return GetStatsForGroup(nsPerformanceGroup::Get(group));
 | |
| }
 | |
| 
 | |
| /* static */ nsIPerformanceStats*
 | |
| nsPerformanceStatsService::GetStatsForGroup(const nsPerformanceGroup* group)
 | |
| {
 | |
|   return new nsPerformanceStats(group->Details(), group->data);
 | |
| }
 | |
| 
 | |
| /* [implicit_jscontext] nsIPerformanceSnapshot getSnapshot (); */
 | |
| NS_IMETHODIMP
 | |
| nsPerformanceStatsService::GetSnapshot(JSContext* cx, nsIPerformanceSnapshot * *aSnapshot)
 | |
| {
 | |
|   if (!mIsAvailable) {
 | |
|     return NS_ERROR_NOT_AVAILABLE;
 | |
|   }
 | |
| 
 | |
|   RefPtr<nsPerformanceSnapshot> snapshot = new nsPerformanceSnapshot();
 | |
|   snapshot->SetProcessStats(GetStatsForGroup(mTopGroup));
 | |
| 
 | |
|   for (auto iter = mGroups.Iter(); !iter.Done(); iter.Next()) {
 | |
|     auto* entry = iter.Get();
 | |
|     nsPerformanceGroup* group = entry->GetKey();
 | |
|     if (group->isActive()) {
 | |
|       snapshot->AppendComponentsStats(GetStatsForGroup(group));
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   js::GetPerfMonitoringTestCpuRescheduling(cx, &mProcessStayed, &mProcessMoved);
 | |
| 
 | |
|   if (++mProcessUpdateCounter % 10 == 0) {
 | |
|     mozilla::Unused << UpdateTelemetry();
 | |
|   }
 | |
| 
 | |
|   snapshot.forget(aSnapshot);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| uint64_t
 | |
| nsPerformanceStatsService::GetNextId() {
 | |
|   return ++mUIdCounter;
 | |
| }
 | |
| 
 | |
| /* static*/ bool
 | |
| nsPerformanceStatsService::GetPerformanceGroupsCallback(JSContext* cx,
 | |
|                                                         js::PerformanceGroupVector& out,
 | |
|                                                         void* closure)
 | |
| {
 | |
|   RefPtr<nsPerformanceStatsService> self = reinterpret_cast<nsPerformanceStatsService*>(closure);
 | |
|   return self->GetPerformanceGroups(cx, out);
 | |
| }
 | |
| 
 | |
| bool
 | |
| nsPerformanceStatsService::GetPerformanceGroups(JSContext* cx,
 | |
|                                                 js::PerformanceGroupVector& out)
 | |
| {
 | |
|   JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
 | |
|   if (!global) {
 | |
|     // While it is possible for a compartment to have no global
 | |
|     // (e.g. atoms), this compartment is not very interesting for us.
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // All compartments belong to the top group.
 | |
|   if (!out.append(mTopGroup)) {
 | |
|     JS_ReportOutOfMemory(cx);
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   nsAutoString name;
 | |
|   RealmName(cx, global, name);
 | |
|   bool isSystem = nsContentUtils::IsSystemPrincipal(nsContentUtils::ObjectPrincipal(global));
 | |
| 
 | |
|   // Find out if the compartment is executed by a window. If so, its
 | |
|   // duration should count towards the total duration of the window.
 | |
|   uint64_t windowId = 0;
 | |
|   if (nsCOMPtr<nsPIDOMWindowOuter> ptop = GetPrivateWindow(cx)) {
 | |
|     windowId = ptop->WindowID();
 | |
|     auto entry = mWindowIdToGroup.PutEntry(windowId);
 | |
|     if (!entry->GetGroup()) {
 | |
|       nsString windowName = name;
 | |
|       windowName.AppendLiteral(" (as window ");
 | |
|       windowName.AppendInt(windowId);
 | |
|       windowName.AppendLiteral(")");
 | |
|       entry->
 | |
|         SetGroup(nsPerformanceGroup::Make(this,
 | |
|                                           windowName, windowId,
 | |
|                                           mProcessId, isSystem,
 | |
|                                           nsPerformanceGroup::GroupScope::WINDOW)
 | |
|                  );
 | |
|     }
 | |
|     if (!out.append(entry->GetGroup())) {
 | |
|       JS_ReportOutOfMemory(cx);
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // All compartments have their own group.
 | |
|   auto group =
 | |
|     nsPerformanceGroup::Make(this,
 | |
|                              name, windowId,
 | |
|                              mProcessId, isSystem,
 | |
|                              nsPerformanceGroup::GroupScope::COMPARTMENT);
 | |
|   if (!out.append(group)) {
 | |
|     JS_ReportOutOfMemory(cx);
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // Returning a vector that is too large would cause allocations all over the
 | |
|   // place in the JS engine. We want to be sure that all data is stored inline.
 | |
|   MOZ_ASSERT(out.length() <= out.sMaxInlineStorage);
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| /*static*/ bool
 | |
| nsPerformanceStatsService::StopwatchStartCallback(uint64_t iteration, void* closure) {
 | |
|   RefPtr<nsPerformanceStatsService> self = reinterpret_cast<nsPerformanceStatsService*>(closure);
 | |
|   return self->StopwatchStart(iteration);
 | |
| }
 | |
| 
 | |
| bool
 | |
| nsPerformanceStatsService::StopwatchStart(uint64_t iteration) {
 | |
|   mIteration = iteration;
 | |
| 
 | |
|   mIsHandlingUserInput = IsHandlingUserInput();
 | |
|   mUserInputCount = mozilla::EventStateManager::UserInputCount();
 | |
| 
 | |
|   nsresult rv = GetResources(&mUserTimeStart, &mSystemTimeStart);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| /*static*/ bool
 | |
| nsPerformanceStatsService::StopwatchCommitCallback(uint64_t iteration,
 | |
|                                                    js::PerformanceGroupVector& recentGroups,
 | |
|                                                    void* closure)
 | |
| {
 | |
|   RefPtr<nsPerformanceStatsService> self = reinterpret_cast<nsPerformanceStatsService*>(closure);
 | |
|   return self->StopwatchCommit(iteration, recentGroups);
 | |
| }
 | |
| 
 | |
| bool
 | |
| nsPerformanceStatsService::StopwatchCommit(uint64_t iteration,
 | |
|                                            js::PerformanceGroupVector& recentGroups)
 | |
| {
 | |
|   MOZ_ASSERT(iteration == mIteration);
 | |
|   MOZ_ASSERT(!recentGroups.empty());
 | |
| 
 | |
|   uint64_t userTimeStop, systemTimeStop;
 | |
|   nsresult rv = GetResources(&userTimeStop, &systemTimeStop);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // `GetResources` is not guaranteed to be monotonic, so round up
 | |
|   // any negative result to 0 milliseconds.
 | |
|   uint64_t userTimeDelta = 0;
 | |
|   if (userTimeStop > mUserTimeStart)
 | |
|     userTimeDelta = userTimeStop - mUserTimeStart;
 | |
| 
 | |
|   uint64_t systemTimeDelta = 0;
 | |
|   if (systemTimeStop > mSystemTimeStart)
 | |
|     systemTimeDelta = systemTimeStop - mSystemTimeStart;
 | |
| 
 | |
|   MOZ_ASSERT(mTopGroup->isUsedInThisIteration());
 | |
|   const uint64_t totalRecentCycles = mTopGroup->recentCycles(iteration);
 | |
| 
 | |
|   const bool isHandlingUserInput = mIsHandlingUserInput || mozilla::EventStateManager::UserInputCount() > mUserInputCount;
 | |
| 
 | |
|   // We should only reach this stage if `group` has had some activity.
 | |
|   MOZ_ASSERT(mTopGroup->recentTicks(iteration) > 0);
 | |
|   for (auto iter = recentGroups.begin(), end = recentGroups.end(); iter != end; ++iter) {
 | |
|     RefPtr<nsPerformanceGroup> group = nsPerformanceGroup::Get(*iter);
 | |
|     CommitGroup(iteration, userTimeDelta, systemTimeDelta, totalRecentCycles, isHandlingUserInput, group);
 | |
|   }
 | |
| 
 | |
|   // Make sure that `group` was treated along with the other items of `recentGroups`.
 | |
|   MOZ_ASSERT(!mTopGroup->isUsedInThisIteration());
 | |
|   MOZ_ASSERT(mTopGroup->recentTicks(iteration) == 0);
 | |
| 
 | |
|   if (!mPendingAlerts.empty()) {
 | |
|     mPendingAlertsCollector->Start(mJankAlertBufferingDelay);
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void
 | |
| nsPerformanceStatsService::CommitGroup(uint64_t iteration,
 | |
|                                        uint64_t totalUserTimeDelta, uint64_t totalSystemTimeDelta,
 | |
|                                        uint64_t totalCyclesDelta,
 | |
|                                        bool isHandlingUserInput,
 | |
|                                        nsPerformanceGroup* group) {
 | |
| 
 | |
|   MOZ_ASSERT(group->isUsedInThisIteration());
 | |
| 
 | |
|   const uint64_t ticksDelta = group->recentTicks(iteration);
 | |
|   const uint64_t cpowTimeDelta = group->recentCPOW(iteration);
 | |
|   const uint64_t cyclesDelta = group->recentCycles(iteration);
 | |
|   group->resetRecentData();
 | |
| 
 | |
|   // We have now performed all cleanup and may `return` at any time without fear of leaks.
 | |
| 
 | |
|   if (group->iteration() != iteration) {
 | |
|     // Stale data, don't commit.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // When we add a group as changed, we immediately set its
 | |
|   // `recentTicks` from 0 to 1.  If we have `ticksDelta == 0` at
 | |
|   // this stage, we have already called `resetRecentData` but we
 | |
|   // haven't removed it from the list.
 | |
|   MOZ_ASSERT(ticksDelta != 0);
 | |
|   MOZ_ASSERT(cyclesDelta <= totalCyclesDelta);
 | |
|   if (cyclesDelta == 0 || totalCyclesDelta == 0) {
 | |
|     // Nothing useful, don't commit.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   double proportion = (double)cyclesDelta / (double)totalCyclesDelta;
 | |
|   MOZ_ASSERT(proportion <= 1);
 | |
| 
 | |
|   const uint64_t userTimeDelta = proportion * totalUserTimeDelta;
 | |
|   const uint64_t systemTimeDelta = proportion * totalSystemTimeDelta;
 | |
| 
 | |
|   group->data.mTotalUserTime += userTimeDelta;
 | |
|   group->data.mTotalSystemTime += systemTimeDelta;
 | |
|   group->data.mTotalCPOWTime += cpowTimeDelta;
 | |
|   group->data.mTicks += ticksDelta;
 | |
| 
 | |
|   const uint64_t totalTimeDelta = userTimeDelta + systemTimeDelta + cpowTimeDelta;
 | |
|   uint64_t duration = 1000;   // 1ms in µs
 | |
|   for (size_t i = 0;
 | |
|        i < mozilla::ArrayLength(group->data.mDurations) && duration < totalTimeDelta;
 | |
|        ++i, duration *= 2) {
 | |
|     group->data.mDurations[i]++;
 | |
|   }
 | |
| 
 | |
|   group->RecordJank(totalTimeDelta);
 | |
|   group->RecordCPOW(cpowTimeDelta);
 | |
|   if (isHandlingUserInput) {
 | |
|     group->RecordUserInput();
 | |
|   }
 | |
| 
 | |
|   if (totalTimeDelta >= mJankAlertThreshold) {
 | |
|     if (!group->HasPendingAlert()) {
 | |
|       if (mPendingAlerts.append(group)) {
 | |
|         group->SetHasPendingAlert(true);
 | |
|       }
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsresult
 | |
| nsPerformanceStatsService::GetResources(uint64_t* userTime,
 | |
|                                         uint64_t* systemTime) const {
 | |
|   MOZ_ASSERT(userTime);
 | |
|   MOZ_ASSERT(systemTime);
 | |
| 
 | |
| #if defined(XP_MACOSX)
 | |
|   // On MacOS X, to get we per-thread data, we need to
 | |
|   // reach into the kernel.
 | |
| 
 | |
|   mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT;
 | |
|   thread_basic_info_data_t info;
 | |
|   mach_port_t port = mach_thread_self();
 | |
|   kern_return_t err =
 | |
|     thread_info(/* [in] targeted thread*/ port,
 | |
|                 /* [in] nature of information*/ THREAD_BASIC_INFO,
 | |
|                 /* [out] thread information */  (thread_info_t)&info,
 | |
|                 /* [inout] number of items */   &count);
 | |
| 
 | |
|   // We do not need ability to communicate with the thread, so
 | |
|   // let's release the port.
 | |
|   mach_port_deallocate(mach_task_self(), port);
 | |
| 
 | |
|   if (err != KERN_SUCCESS)
 | |
|     return NS_ERROR_FAILURE;
 | |
| 
 | |
|   *userTime = info.user_time.microseconds + info.user_time.seconds * 1000000;
 | |
|   *systemTime = info.system_time.microseconds + info.system_time.seconds * 1000000;
 | |
| 
 | |
| #elif defined(XP_UNIX)
 | |
|   struct rusage rusage;
 | |
| #if defined(RUSAGE_THREAD)
 | |
|   // Under Linux, we can obtain per-thread statistics
 | |
|   int err = getrusage(RUSAGE_THREAD, &rusage);
 | |
| #else
 | |
|   // Under other Unices, we need to do with more noisy
 | |
|   // per-process statistics.
 | |
|   int err = getrusage(RUSAGE_SELF, &rusage);
 | |
| #endif // defined(RUSAGE_THREAD)
 | |
| 
 | |
|   if (err)
 | |
|     return NS_ERROR_FAILURE;
 | |
| 
 | |
|   *userTime = rusage.ru_utime.tv_usec + rusage.ru_utime.tv_sec * 1000000;
 | |
|   *systemTime = rusage.ru_stime.tv_usec + rusage.ru_stime.tv_sec * 1000000;
 | |
| 
 | |
| #elif defined(XP_WIN)
 | |
|   // Under Windows, we can obtain per-thread statistics. Experience
 | |
|   // seems to suggest that they are not very accurate under Windows
 | |
|   // XP, though.
 | |
|   FILETIME creationFileTime; // Ignored
 | |
|   FILETIME exitFileTime; // Ignored
 | |
|   FILETIME kernelFileTime;
 | |
|   FILETIME userFileTime;
 | |
|   BOOL success = GetThreadTimes(GetCurrentThread(),
 | |
|                                 &creationFileTime, &exitFileTime,
 | |
|                                 &kernelFileTime, &userFileTime);
 | |
| 
 | |
|   if (!success)
 | |
|     return NS_ERROR_FAILURE;
 | |
| 
 | |
|   ULARGE_INTEGER kernelTimeInt;
 | |
|   kernelTimeInt.LowPart = kernelFileTime.dwLowDateTime;
 | |
|   kernelTimeInt.HighPart = kernelFileTime.dwHighDateTime;
 | |
|   // Convert 100 ns to 1 us.
 | |
|   *systemTime = kernelTimeInt.QuadPart / 10;
 | |
| 
 | |
|   ULARGE_INTEGER userTimeInt;
 | |
|   userTimeInt.LowPart = userFileTime.dwLowDateTime;
 | |
|   userTimeInt.HighPart = userFileTime.dwHighDateTime;
 | |
|   // Convert 100 ns to 1 us.
 | |
|   *userTime = userTimeInt.QuadPart / 10;
 | |
| 
 | |
| #endif // defined(XP_MACOSX) || defined(XP_UNIX) || defined(XP_WIN)
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void
 | |
| nsPerformanceStatsService::NotifyJankObservers(const mozilla::Vector<uint64_t>& aPreviousJankLevels) {
 | |
| 
 | |
|   // The move operation is generally constant time, unless
 | |
|   // `mPendingAlerts.length()` is very small, in which case it's fast anyway.
 | |
|   GroupVector alerts(std::move(mPendingAlerts));
 | |
|   mPendingAlerts = GroupVector(); // Reconstruct after `Move`.
 | |
| 
 | |
|   if (!mPendingAlertsCollector) {
 | |
|     // We are shutting down.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Find out if we have noticed any user-noticeable delay in an
 | |
|   // animation recently (i.e. since the start of the execution of JS
 | |
|   // code that caused this collector to start). If so, we'll mark any
 | |
|   // alert as part of a user-noticeable jank. Note that this doesn't
 | |
|   // mean with any certainty that the alert is the only cause of jank,
 | |
|   // or even the main cause of jank.
 | |
|   mozilla::Vector<uint64_t> latestJankLevels;
 | |
|   {
 | |
|     mozilla::DebugOnly<bool> result = nsRefreshDriver::GetJankLevels(latestJankLevels);
 | |
|     MOZ_ASSERT(result);
 | |
|   }
 | |
|   MOZ_ASSERT(latestJankLevels.length() == aPreviousJankLevels.length());
 | |
| 
 | |
|   bool isJankInAnimation = false;
 | |
|   for (size_t i = mJankLevelVisibilityThreshold; i < latestJankLevels.length(); ++i) {
 | |
|     if (latestJankLevels[i] > aPreviousJankLevels[i]) {
 | |
|       isJankInAnimation = true;
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT(!alerts.empty());
 | |
|   const bool hasUniversalWindowObservers = mUniversalTargets.mWindows->HasObservers();
 | |
|   for (auto iter = alerts.begin(); iter < alerts.end(); ++iter) {
 | |
|     MOZ_ASSERT(iter);
 | |
|     RefPtr<nsPerformanceGroup> group = *iter;
 | |
|     group->SetHasPendingAlert(false);
 | |
| 
 | |
|     RefPtr<nsPerformanceGroupDetails> details = group->Details();
 | |
|     nsPerformanceObservationTarget* targets[3] = {
 | |
|       hasUniversalWindowObservers && details->IsWindow() ? mUniversalTargets.mWindows.get() : nullptr,
 | |
|       group->ObservationTarget()
 | |
|     };
 | |
| 
 | |
|     bool isJankInInput = group->HasRecentUserInput();
 | |
| 
 | |
|     RefPtr<PerformanceAlert> alert;
 | |
|     for (nsPerformanceObservationTarget* target : targets) {
 | |
|       if (!target) {
 | |
|         continue;
 | |
|       }
 | |
|       if (!alert) {
 | |
|         const uint32_t reason = nsIPerformanceAlert::REASON_SLOWDOWN
 | |
|           | (isJankInAnimation ? nsIPerformanceAlert::REASON_JANK_IN_ANIMATION : 0)
 | |
|           | (isJankInInput ? nsIPerformanceAlert::REASON_JANK_IN_INPUT : 0);
 | |
|         // Wait until we are sure we need to allocate before we allocate.
 | |
|         alert = new PerformanceAlert(reason, group);
 | |
|       }
 | |
|       target->NotifyJankObservers(details, alert);
 | |
|     }
 | |
| 
 | |
|     group->ResetRecent();
 | |
|   }
 | |
| 
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsPerformanceStatsService::GetObservableWindow(uint64_t windowId,
 | |
|                                                nsIPerformanceObservable** result) {
 | |
|   if (windowId == 0) {
 | |
|     NS_IF_ADDREF(*result = mUniversalTargets.mWindows);
 | |
|   } else {
 | |
|     auto entry = mWindowIdToGroup.PutEntry(windowId);
 | |
|     NS_IF_ADDREF(*result = entry->ObservationTarget());
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsPerformanceStatsService::GetAnimationJankLevelThreshold(short* result) {
 | |
|   *result = mJankLevelVisibilityThreshold;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsPerformanceStatsService::SetAnimationJankLevelThreshold(short value) {
 | |
|   mJankLevelVisibilityThreshold = value;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsPerformanceStatsService::GetUserInputDelayThreshold(uint64_t* result) {
 | |
|   *result = mMaxExpectedDurationOfInteractionUS;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsPerformanceStatsService::SetUserInputDelayThreshold(uint64_t value) {
 | |
|   mMaxExpectedDurationOfInteractionUS = value;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| nsPerformanceStatsService::UniversalTargets::UniversalTargets()
 | |
|   : mWindows(new nsPerformanceObservationTarget())
 | |
| { }
 | |
| 
 | |
| /* ------------------------------------------------------
 | |
|  *
 | |
|  * Class nsPerformanceGroup
 | |
|  *
 | |
|  */
 | |
| 
 | |
| /*static*/ nsPerformanceGroup*
 | |
| nsPerformanceGroup::Make(nsPerformanceStatsService* service,
 | |
|                          const nsAString& name,
 | |
|                          uint64_t windowId,
 | |
|                          uint64_t processId,
 | |
|                          bool isSystem,
 | |
|                          GroupScope scope)
 | |
| {
 | |
|   nsString groupId;
 | |
|   ::GenerateUniqueGroupId(service->GetNextId(), processId, groupId);
 | |
|   return new nsPerformanceGroup(service, name, groupId, windowId, processId, isSystem, scope);
 | |
| }
 | |
| 
 | |
| nsPerformanceGroup::nsPerformanceGroup(nsPerformanceStatsService* service,
 | |
|                                        const nsAString& name,
 | |
|                                        const nsAString& groupId,
 | |
|                                        uint64_t windowId,
 | |
|                                        uint64_t processId,
 | |
|                                        bool isSystem,
 | |
|                                        GroupScope scope)
 | |
|   : mDetails(new nsPerformanceGroupDetails(name, groupId, windowId, processId, isSystem))
 | |
|   , mService(service)
 | |
|   , mScope(scope)
 | |
|   , mHighestJank(0)
 | |
|   , mHighestCPOW(0)
 | |
|   , mHasRecentUserInput(false)
 | |
|   , mHasPendingAlert(false)
 | |
| {
 | |
|   mozilla::Unused << mService->mGroups.PutEntry(this);
 | |
| 
 | |
| #if defined(DEBUG)
 | |
|   if (scope == GroupScope::WINDOW) {
 | |
|     MOZ_ASSERT(mDetails->IsWindow());
 | |
|   } else if (scope == GroupScope::RUNTIME) {
 | |
|     MOZ_ASSERT(!mDetails->IsWindow());
 | |
|   }
 | |
| #endif // defined(DEBUG)
 | |
|   setIsActive(mScope != GroupScope::COMPARTMENT || mService->mIsMonitoringPerCompartment);
 | |
| }
 | |
| 
 | |
| void
 | |
| nsPerformanceGroup::Dispose() {
 | |
|   if (!mService) {
 | |
|     // We have already called `Dispose()`.
 | |
|     return;
 | |
|   }
 | |
|   if (mObservationTarget) {
 | |
|     mObservationTarget = nullptr;
 | |
|   }
 | |
| 
 | |
|   // Remove any reference to the service.
 | |
|   RefPtr<nsPerformanceStatsService> service;
 | |
|   service.swap(mService);
 | |
| 
 | |
|   // Remove any dangling pointer to `this`.
 | |
|   service->mGroups.RemoveEntry(this);
 | |
| 
 | |
|   if (mScope == GroupScope::WINDOW) {
 | |
|     MOZ_ASSERT(mDetails->IsWindow());
 | |
|     service->mWindowIdToGroup.RemoveEntry(mDetails->WindowId());
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsPerformanceGroup::~nsPerformanceGroup() {
 | |
|   Dispose();
 | |
| }
 | |
| 
 | |
| nsPerformanceGroup::GroupScope
 | |
| nsPerformanceGroup::Scope() const {
 | |
|   return mScope;
 | |
| }
 | |
| 
 | |
| nsPerformanceGroupDetails*
 | |
| nsPerformanceGroup::Details() const {
 | |
|   return mDetails;
 | |
| }
 | |
| 
 | |
| void
 | |
| nsPerformanceGroup::SetObservationTarget(nsPerformanceObservationTarget* target) {
 | |
|   MOZ_ASSERT(!mObservationTarget);
 | |
|   mObservationTarget = target;
 | |
| }
 | |
| 
 | |
| nsPerformanceObservationTarget*
 | |
| nsPerformanceGroup::ObservationTarget() const {
 | |
|   return mObservationTarget;
 | |
| }
 | |
| 
 | |
| bool
 | |
| nsPerformanceGroup::HasPendingAlert() const {
 | |
|   return mHasPendingAlert;
 | |
| }
 | |
| 
 | |
| void
 | |
| nsPerformanceGroup::SetHasPendingAlert(bool value) {
 | |
|   mHasPendingAlert = value;
 | |
| }
 | |
| 
 | |
| 
 | |
| void
 | |
| nsPerformanceGroup::RecordJank(uint64_t jank) {
 | |
|   if (jank > mHighestJank) {
 | |
|     mHighestJank = jank;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void
 | |
| nsPerformanceGroup::RecordCPOW(uint64_t cpow) {
 | |
|   if (cpow > mHighestCPOW) {
 | |
|     mHighestCPOW = cpow;
 | |
|   }
 | |
| }
 | |
| 
 | |
| uint64_t
 | |
| nsPerformanceGroup::HighestRecentJank() {
 | |
|   return mHighestJank;
 | |
| }
 | |
| 
 | |
| uint64_t
 | |
| nsPerformanceGroup::HighestRecentCPOW() {
 | |
|   return mHighestCPOW;
 | |
| }
 | |
| 
 | |
| bool
 | |
| nsPerformanceGroup::HasRecentUserInput() {
 | |
|   return mHasRecentUserInput;
 | |
| }
 | |
| 
 | |
| void
 | |
| nsPerformanceGroup::RecordUserInput() {
 | |
|   mHasRecentUserInput = true;
 | |
| }
 | |
| 
 | |
| void
 | |
| nsPerformanceGroup::ResetRecent() {
 | |
|   mHighestJank = 0;
 | |
|   mHighestCPOW = 0;
 | |
|   mHasRecentUserInput = false;
 | |
| }
 | 
