diff options
Diffstat (limited to 'toolkit/components/perfmonitoring/nsPerformanceStats.cpp')
-rw-r--r-- | toolkit/components/perfmonitoring/nsPerformanceStats.cpp | 1620 |
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; +} |