summaryrefslogtreecommitdiffstats
path: root/toolkit/components/perfmonitoring/nsPerformanceStats.cpp
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /toolkit/components/perfmonitoring/nsPerformanceStats.cpp
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'toolkit/components/perfmonitoring/nsPerformanceStats.cpp')
-rw-r--r--toolkit/components/perfmonitoring/nsPerformanceStats.cpp1620
1 files changed, 1620 insertions, 0 deletions
diff --git a/toolkit/components/perfmonitoring/nsPerformanceStats.cpp b/toolkit/components/perfmonitoring/nsPerformanceStats.cpp
new file mode 100644
index 000000000..eb924de46
--- /dev/null
+++ b/toolkit/components/perfmonitoring/nsPerformanceStats.cpp
@@ -0,0 +1,1620 @@
+/* -*- 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 "mozilla/Unused.h"
+#include "mozilla/ArrayUtils.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) {
+ nsGlobalWindow* 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
+CompartmentName(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::GetCurrentCompartmentName(cx, cname);
+ name.Assign(NS_ConvertUTF8toUTF16(cname));
+}
+
+/**
+ * Generate a unique-to-the-application identifier for a group.
+ */
+void
+GenerateUniqueGroupId(const JSContext* cx, uint64_t uid, uint64_t processId, nsAString& groupId) {
+ uint64_t contextId = reinterpret_cast<uintptr_t>(cx);
+
+ groupId.AssignLiteral("process: ");
+ groupId.AppendInt(processId);
+ groupId.AppendLiteral(", thread: ");
+ groupId.AppendInt(contextId);
+ 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;
+}
+
+const nsAString&
+nsPerformanceGroupDetails::AddonId() const {
+ return mAddonId;
+}
+
+uint64_t
+nsPerformanceGroupDetails::WindowId() const {
+ return mWindowId;
+}
+
+uint64_t
+nsPerformanceGroupDetails::ProcessId() const {
+ return mProcessId;
+}
+
+bool
+nsPerformanceGroupDetails::IsSystem() const {
+ return mIsSystem;
+}
+
+bool
+nsPerformanceGroupDetails::IsAddon() const {
+ return mAddonId.Length() != 0;
+}
+
+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 AString addonId; */
+NS_IMETHODIMP
+nsPerformanceGroupDetails::GetAddonId(nsAString& aAddonId) {
+ aAddonId.Assign(AddonId());
+ 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, false);
+ 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:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+
+ 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);
+
+NS_IMETHODIMP
+PendingAlertsCollector::Notify(nsITimer*) {
+ mPending = false;
+ mService->NotifyJankObservers(mJankLevels);
+ return NS_OK;
+}
+
+nsresult
+PendingAlertsCollector::Start(uint32_t timerDelayMS) {
+ if (mPending) {
+ // Collector is already started.
+ return NS_OK;
+ }
+
+ if (!mTimer) {
+ mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ }
+
+ 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
+ , mContext(mozilla::dom::danger::GetJSContext())
+ , mUIdCounter(0)
+ , mTopGroup(nsPerformanceGroup::Make(mContext,
+ this,
+ NS_LITERAL_STRING("<process>"), // name
+ NS_LITERAL_STRING(""), // addonid
+ 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);
+
+ // Attach some artificial group information to the universal listeners, to aid with debugging.
+ nsString groupIdForAddons;
+ GenerateUniqueGroupId(mContext, GetNextId(), mProcessId, groupIdForAddons);
+ mUniversalTargets.mAddons->
+ SetTarget(new nsPerformanceGroupDetails(NS_LITERAL_STRING("<universal add-on listener>"),
+ groupIdForAddons,
+ NS_LITERAL_STRING("<universal add-on listener>"),
+ 0, // window id
+ mProcessId,
+ false));
+
+
+ nsString groupIdForWindows;
+ GenerateUniqueGroupId(mContext, GetNextId(), mProcessId, groupIdForWindows);
+ mUniversalTargets.mWindows->
+ SetTarget(new nsPerformanceGroupDetails(NS_LITERAL_STRING("<universal window listener>"),
+ groupIdForWindows,
+ NS_LITERAL_STRING("<universal window listener>"),
+ 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.
+ JSContext* cx = mContext;
+ 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.mAddons = nullptr;
+ 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.
+ JSContext* cx = mContext;
+ 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;
+ CompartmentName(cx, global, name);
+ bool isSystem = nsContentUtils::IsSystemPrincipal(nsContentUtils::ObjectPrincipal(global));
+
+ // Find out if the compartment is executed by an add-on. If so, its
+ // duration should count towards the total duration of the add-on.
+ JSAddonId* jsaddonId = AddonIdOfObject(global);
+ nsString addonId;
+ if (jsaddonId) {
+ AssignJSFlatString(addonId, (JSFlatString*)jsaddonId);
+ auto entry = mAddonIdToGroup.PutEntry(addonId);
+ if (!entry->GetGroup()) {
+ nsString addonName = name;
+ addonName.AppendLiteral(" (as addon ");
+ addonName.Append(addonId);
+ addonName.AppendLiteral(")");
+ entry->
+ SetGroup(nsPerformanceGroup::Make(mContext, this,
+ addonName, addonId, 0,
+ mProcessId, isSystem,
+ nsPerformanceGroup::GroupScope::ADDON)
+ );
+ }
+ if (!out.append(entry->GetGroup())) {
+ JS_ReportOutOfMemory(cx);
+ return false;
+ }
+ }
+
+ // 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(mContext, this,
+ windowName, EmptyString(), 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(mContext, this,
+ name, addonId, windowId,
+ mProcessId, isSystem,
+ nsPerformanceGroup::GroupScope::COMPARTMENT);
+ if (!out.append(group)) {
+ JS_ReportOutOfMemory(cx);
+ return false;
+ }
+
+ 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;
+ }
+ }
+
+ 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) {
+ GroupVector alerts;
+ mPendingAlerts.swap(alerts);
+ 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 hasUniversalAddonObservers = mUniversalTargets.mAddons->HasObservers();
+ 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] = {
+ hasUniversalAddonObservers && details->IsAddon() ? mUniversalTargets.mAddons.get() : nullptr,
+ 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::GetObservableAddon(const nsAString& addonId,
+ nsIPerformanceObservable** result) {
+ if (addonId.Equals(NS_LITERAL_STRING("*"))) {
+ NS_IF_ADDREF(*result = mUniversalTargets.mAddons);
+ } else {
+ auto entry = mAddonIdToGroup.PutEntry(addonId);
+ NS_IF_ADDREF(*result = entry->ObservationTarget());
+ }
+ return NS_OK;
+}
+
+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()
+ : mAddons(new nsPerformanceObservationTarget())
+ , mWindows(new nsPerformanceObservationTarget())
+{ }
+
+/* ------------------------------------------------------
+ *
+ * Class nsPerformanceGroup
+ *
+ */
+
+/*static*/ nsPerformanceGroup*
+nsPerformanceGroup::Make(JSContext* cx,
+ nsPerformanceStatsService* service,
+ const nsAString& name,
+ const nsAString& addonId,
+ uint64_t windowId,
+ uint64_t processId,
+ bool isSystem,
+ GroupScope scope)
+{
+ nsString groupId;
+ ::GenerateUniqueGroupId(cx, service->GetNextId(), processId, groupId);
+ return new nsPerformanceGroup(service, name, groupId, addonId, windowId, processId, isSystem, scope);
+}
+
+nsPerformanceGroup::nsPerformanceGroup(nsPerformanceStatsService* service,
+ const nsAString& name,
+ const nsAString& groupId,
+ const nsAString& addonId,
+ uint64_t windowId,
+ uint64_t processId,
+ bool isSystem,
+ GroupScope scope)
+ : mDetails(new nsPerformanceGroupDetails(name, groupId, addonId, 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::ADDON) {
+ MOZ_ASSERT(mDetails->IsAddon());
+ MOZ_ASSERT(!mDetails->IsWindow());
+ } else if (scope == GroupScope::WINDOW) {
+ MOZ_ASSERT(mDetails->IsWindow());
+ MOZ_ASSERT(!mDetails->IsAddon());
+ } else if (scope == GroupScope::RUNTIME) {
+ MOZ_ASSERT(!mDetails->IsWindow());
+ MOZ_ASSERT(!mDetails->IsAddon());
+ }
+#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::ADDON) {
+ MOZ_ASSERT(mDetails->IsAddon());
+ service->mAddonIdToGroup.RemoveEntry(mDetails->AddonId());
+ } else 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;
+}