Bug 1219144 - Using the nsRefreshDriver's jank indication for performance monitoring;f?froydnj
This patch (currently WIP) alters the way we determine whether jank is user-visible or not.
Instead of measuring the total time spent doing JS, we now use an
indicator provided by the vsync driver: how long it takes to deliver
the signal from the vsync timer to the main thread. This lets us find
out more accurately if there is user-visible jank. In the future, this
will also let us add an observer to find out whether the process
itself is janky, regardless of JS.
--- a/layout/base/nsRefreshDriver.cpp
+++ b/layout/base/nsRefreshDriver.cpp
@@ -90,16 +90,23 @@ namespace {
// In jank-critical mode, any iteration of the event loop that takes
// more than 16ms to compute will cause an ongoing animation to miss
// frames.
//
// For simplicity, the current implementation assumes that we are in
// jank-critical mode if and only if at least one vsync driver has
// at least one observer.
static uint64_t sActiveVsyncTimers = 0;
+
+ // The latest value of process-wide jank levels.
+ //
+ // For each i, sJankLevels[i] counts the number of times delivery of
+ // vsync to the main thread has been delayed by at least 2^i ms. Use
+ // GetJankLevels to grab a copy of this array.
+ uint64_t sJankLevels[12];
}
namespace mozilla {
/*
* The base class for all global refresh driver timers. It takes care
* of managing the list of refresh drivers attached to them and
* provides interfaces for querying/setting the rate and actually
@@ -441,36 +448,48 @@ private:
#ifndef ANDROID /* bug 1142079 */
if (XRE_IsParentProcess()) {
TimeDuration vsyncLatency = TimeStamp::Now() - aVsyncTimestamp;
uint32_t sample = (uint32_t)vsyncLatency.ToMilliseconds();
Telemetry::Accumulate(Telemetry::FX_REFRESH_DRIVER_CHROME_FRAME_DELAY_MS,
sample);
Telemetry::Accumulate(Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS,
sample);
+ RecordJank(sample);
} else if (mVsyncRate != TimeDuration::Forever()) {
TimeDuration contentDelay = (TimeStamp::Now() - mLastChildTick) - mVsyncRate;
if (contentDelay.ToMilliseconds() < 0 ){
// Vsyncs are noisy and some can come at a rate quicker than
// the reported hardware rate. In those cases, consider that we have 0 delay.
contentDelay = TimeDuration::FromMilliseconds(0);
}
uint32_t sample = (uint32_t)contentDelay.ToMilliseconds();
Telemetry::Accumulate(Telemetry::FX_REFRESH_DRIVER_CONTENT_FRAME_DELAY_MS,
sample);
Telemetry::Accumulate(Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS,
sample);
+ RecordJank(sample);
} else {
// Request the vsync rate from the parent process. Might be a few vsyncs
// until the parent responds.
mVsyncRate = mVsyncRefreshDriverTimer->mVsyncChild->GetVsyncRate();
}
#endif
}
+ void RecordJank(uint32_t aJankMS)
+ {
+ uint32_t duration = 1 /* ms */;
+ for (size_t i = 0;
+ i < mozilla::ArrayLength(sJankLevels) && duration < aJankMS;
+ ++i, duration *= 2) {
+ sJankLevels[i]++;
+ }
+ }
+
void TickRefreshDriver(TimeStamp aVsyncTimestamp)
{
MOZ_ASSERT(NS_IsMainThread());
RecordTelemetryProbes(aVsyncTimestamp);
if (XRE_IsParentProcess()) {
MonitorAutoLock lock(mRefreshTickLock);
aVsyncTimestamp = mRecentVsync;
@@ -816,16 +835,17 @@ CreateContentVsyncRefreshTimer(void*)
}
}
static void
CreateVsyncRefreshTimer()
{
MOZ_ASSERT(NS_IsMainThread());
+ PodArrayZero(sJankLevels);
// Sometimes, gfxPrefs is not initialized here. Make sure the gfxPrefs is
// ready.
gfxPrefs::GetSingleton();
if (gfxPlatform::IsInLayoutAsapMode()) {
return;
}
@@ -2156,9 +2176,15 @@ nsRefreshDriver::CancelPendingEvents(nsI
/* static */ bool
nsRefreshDriver::IsJankCritical()
{
MOZ_ASSERT(NS_IsMainThread());
return sActiveVsyncTimers > 0;
}
+/* static */ bool
+nsRefreshDriver::GetJankLevels(Vector<uint64_t>& aJank) {
+ aJank.clear();
+ return aJank.append(sJankLevels, ArrayLength(sJankLevels));
+}
+
#undef LOG
--- a/layout/base/nsRefreshDriver.h
+++ b/layout/base/nsRefreshDriver.h
@@ -8,16 +8,18 @@
* Code to notify things that animate before a refresh, at an appropriate
* refresh rate. (Perhaps temporary, until replaced by compositor.)
*/
#ifndef nsRefreshDriver_h_
#define nsRefreshDriver_h_
#include "mozilla/TimeStamp.h"
+#include "mozilla/Vector.h"
+
#include "mozFlushType.h"
#include "nsTObserverArray.h"
#include "nsTArray.h"
#include "nsTHashtable.h"
#include "nsTObserverArray.h"
#include "nsClassHashtable.h"
#include "nsHashKeys.h"
#include "mozilla/Attributes.h"
@@ -291,16 +293,30 @@ public:
/**
* Default interval the refresh driver uses, in ms.
*/
static int32_t DefaultInterval();
bool IsInRefresh() { return mInRefresh; }
+ /**
+ * The latest value of process-wide jank levels.
+ *
+ * For each i, sJankLevels[i] counts the number of times delivery of
+ * vsync to the main thread has been delayed by at least 2^i
+ * ms. This data structure has been designed to make it easy to
+ * determine how much jank has taken place between two instants in
+ * time.
+ *
+ * Return `false` if `aJank` needs to be grown to accomodate the
+ * data but we didn't have enough memory.
+ */
+ static bool GetJankLevels(mozilla::Vector<uint64_t>& aJank);
+
// mozilla::layers::TransactionIdAllocator
virtual uint64_t GetTransactionId() override;
void NotifyTransactionCompleted(uint64_t aTransactionId) override;
void RevokeTransactionId(uint64_t aTransactionId) override;
mozilla::TimeStamp GetTransactionStart() override;
bool IsWaitingForPaint(mozilla::TimeStamp aTime);
--- a/toolkit/components/perfmonitoring/nsIPerformanceStats.idl
+++ b/toolkit/components/perfmonitoring/nsIPerformanceStats.idl
@@ -121,48 +121,81 @@ interface nsIPerformanceSnapshot: nsISup
* inter-process calls, etc.
*/
nsIPerformanceStats getProcessData();
};
/**
* A performance alert.
*/
-[scriptable, builtinclass, uuid(d5379791-3f90-4edc-bcbb-5d4edea03e57)]
+[scriptable, builtinclass, uuid(a85706ab-d703-4687-8865-78cd771eab93)]
interface nsIPerformanceAlert: nsISupports {
- // Alert was caused by jank exceeding the threshold.
- const unsigned long REASON_JANK = 0;
+ /**
+ * A slowdown was detected.
+ *
+ * See REASON_JANK_* for details on whether this slowdown was user-noticeable.
+ */
+ const unsigned long REASON_SLOWDOWN = 1;
/**
- * The reason for the alert.
+ * This alert was triggered during a jank in animation.
+ *
+ * In the current implementation, we consider that there is a jank
+ * in animation if delivery of the vsync message to the main thread
+ * has been delayed too much (see
+ * nsIPerformanceStatsService.animationJankLevelThreshold).
+ *
+ * Note that this is a heuristic which may provide false positives,
+ * so clients of this API are expected to perform post-processing to
+ * filter out such false positives.
+ */
+ const unsigned long REASON_JANK_IN_ANIMATION = 2;
+
+ /**
+ * This alert was triggered during a jank in user input.
+ *
+ * In the current implementation, we consider that there is a jank
+ * in animation if a user input was received either immediately
+ * before executing the offending code (see
+ * nsIPerformanceStatsService.userInputDelayThreshold) or while
+ * executing the offending code.
+ *
+ * Note that this is a heuristic which may provide false positives,
+ * so clients of this API are expected to perform post-processing to
+ * filter out such false positives.
+ */
+ const unsigned long REASON_JANK_IN_INPUT = 4;
+
+ /**
+ * The reason for the alert, as a bitwise or of the various REASON_*
+ * constants.
*/
readonly attribute unsigned long reason;
/**
* Longest interval spent executing code in this group
* since the latest alert, in microseconds.
+ *
+ * Note that the underlying algorithm is probabilistic and may
+ * provide false positives, so clients of this API are expected to
+ * perform post-processing to filter out such false positives. In
+ * particular, a high system load will increase the noise level on
+ * this measure.
*/
readonly attribute unsigned long long highestJank;
/**
* Longest interval spent executing CPOW in this group
* since the latest alert, in microseconds.
+ *
+ * This measure is reliable and involves no heuristics. However,
+ * note that the duration of CPOWs is increased by high system
+ * loads.
*/
readonly attribute unsigned long long highestCPOW;
-
- /**
- * `true` if the jank is expected to be noticeable by the user,
- * `false` otherwise.
- *
- * In the current implementation, we consider that jank is noticeable
- * if it arrives during an animation, shortly after a user input, or
- * if a user input is triggered during the same iteration of the event
- * loop.
- */
- readonly attribute bool isJankVisible;
};
/**
* An observer for slow performance alerts.
*/
[scriptable, function, uuid(b746a929-3fec-420b-8ed8-c35d71995e05)]
interface nsIPerformanceObserver: nsISupports {
@@ -199,17 +232,17 @@ interface nsIPerformanceObservable: nsIS
* Remove an observer previously added with `addJankObserver`.
*
* Noop if the observer hasn't been added.
*/
void removeJankObserver(in nsIPerformanceObserver observer);
};
-[scriptable, uuid(5675d1d9-638e-4af0-9392-b04aacfad79a)]
+[scriptable, uuid(505bc42e-be38-4a53-baba-92cb33690cde)]
interface nsIPerformanceStatsService : nsISupports {
/**
* `true` if we should monitor CPOW, `false` otherwise.
*/
[implicit_jscontext] attribute bool isMonitoringCPOW;
/**
* `true` if we should monitor jank, `false` otherwise.
@@ -230,16 +263,36 @@ interface nsIPerformanceStatsService : n
/**
* The threshold, in microseconds, above which a performance group is
* considered "slow" and should raise performance alerts.
*/
attribute unsigned long long jankAlertThreshold;
/**
+ * If a user is seeing an animation and we spend too long executing
+ * JS code while blocking refresh, this will be visible to the user.
+ *
+ * We assume that any jank during an animation and lasting more than
+ * 2^animationJankLevelThreshold ms will be visible.
+ */
+ attribute short animationJankLevelThreshold;
+
+ /**
+ * If a user performs an input (e.g. clicking, pressing a key, but
+ * *NOT* moving the mouse), and we spend too long executing JS code
+ * before displaying feedback, this will be visible to the user even
+ * if there is no ongoing animation.
+ *
+ * We assume that any jank during `userInputDelayThreshold` us after
+ * the user input will be visible.
+ */
+ attribute unsigned long long userInputDelayThreshold;
+
+ /**
* A buffering delay, in milliseconds, used by the service to
* regroup performance alerts, before observers are actually
* noticed. Higher delays let the system avoid redundant
* notifications for the same group, and are generally better for
* performance.
*/
attribute unsigned long jankAlertBufferingDelay;
--- a/toolkit/components/perfmonitoring/nsPerformanceStats.cpp
+++ b/toolkit/components/perfmonitoring/nsPerformanceStats.cpp
@@ -517,28 +517,24 @@ 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;
-
- // `true` if jank may be noticed by the user.
- const bool mIsJankVisible;
};
NS_IMPL_ISUPPORTS(PerformanceAlert, nsIPerformanceAlert);
PerformanceAlert::PerformanceAlert(const uint32_t reason, nsPerformanceGroup* source)
: mReason(reason)
, mHighestJank(source->HighestRecentJank())
, mHighestCPOW(source->HighestRecentCPOW())
- , mIsJankVisible(source->RecentJankVisibility())
{ }
NS_IMETHODIMP
PerformanceAlert::GetHighestJank(uint64_t* result) {
*result = mHighestJank;
return NS_OK;
}
@@ -548,22 +544,16 @@ PerformanceAlert::GetHighestCPOW(uint64_
return NS_OK;
}
NS_IMETHODIMP
PerformanceAlert::GetReason(uint32_t* result) {
*result = mReason;
return NS_OK;
}
-
-NS_IMETHODIMP
-PerformanceAlert::GetIsJankVisible(bool* result) {
- *result = mIsJankVisible;
- return NS_OK;
-}
/* ------------------------------------------------------
*
* class PendingAlertsCollector
*
*/
/**
* A timer callback in charge of collecting the groups in
@@ -576,31 +566,34 @@ public:
PendingAlertsCollector(JSRuntime* runtime, 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();
+ mService->NotifyJankObservers(mJankLevels);
return NS_OK;
}
nsresult
PendingAlertsCollector::Start(uint32_t timerDelayMS) {
if (mPending) {
// Collector is already started.
return NS_OK;
@@ -611,16 +604,21 @@ PendingAlertsCollector::Start(uint32_t t
}
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;
@@ -652,23 +650,25 @@ nsPerformanceStatsService::nsPerformance
this,
NS_LITERAL_STRING("<process>"), // name
NS_LITERAL_STRING(""), // addonid
0, // windowId
mProcessId,
true, // isSystem
nsPerformanceGroup::GroupScope::RUNTIME // scope
))
- , mIsJankCritical(false)
+ , 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(mRuntime, this);
// Attach some artificial group information to the universal listeners, to aid with debugging.
nsString groupIdForAddons;
GenerateUniqueGroupId(mRuntime, GetNextId(), mProcessId, groupIdForAddons);
mUniversalTargets.mAddons->
SetTarget(new nsPerformanceGroupDetails(NS_LITERAL_STRING("<universal add-on listener>"),
@@ -816,40 +816,25 @@ nsPerformanceStatsService::Observe(nsISu
|| strcmp(aTopic, "quit-application-granted") == 0
|| strcmp(aTopic, "content-child-shutdown") == 0
|| strcmp(aTopic, "xpcom-will-shutdown") == 0);
Dispose();
return NS_OK;
}
-/*static*/
-bool
+/*static*/ bool
nsPerformanceStatsService::IsHandlingUserInput() {
if (mozilla::EventStateManager::LatestUserInputStart().IsNull()) {
return false;
}
- bool result = mozilla::TimeStamp::Now() - mozilla::EventStateManager::LatestUserInputStart() <= mozilla::TimeDuration::FromMilliseconds(MAX_DURATION_OF_INTERACTION_MS);
- return result;
-}
-
-/*static*/ bool
-nsPerformanceStatsService::IsJankCritical() {
- bool result = IsAnimating() || IsHandlingUserInput();
+ bool result = mozilla::TimeStamp::Now() - mozilla::EventStateManager::LatestUserInputStart() <= mozilla::TimeDuration::FromMicroseconds(mMaxExpectedDurationOfInteractionUS);
return result;
}
-/*static*/ bool
-nsPerformanceStatsService::IsAnimating() {
- bool result = nsRefreshDriver::IsJankCritical();
- return result;
-}
-
-
-
/* [implicit_jscontext] attribute bool isMonitoringCPOW; */
NS_IMETHODIMP
nsPerformanceStatsService::GetIsMonitoringCPOW(JSContext* cx, bool *aIsStopwatchActive)
{
if (!mIsAvailable) {
return NS_ERROR_NOT_AVAILABLE;
}
@@ -1127,17 +1112,17 @@ nsPerformanceStatsService::StopwatchStar
RefPtr<nsPerformanceStatsService> self = reinterpret_cast<nsPerformanceStatsService*>(closure);
return self->StopwatchStart(iteration);
}
bool
nsPerformanceStatsService::StopwatchStart(uint64_t iteration) {
mIteration = iteration;
- mIsJankCritical = IsJankCritical();
+ mIsHandlingUserInput = IsHandlingUserInput();
mUserInputCount = mozilla::EventStateManager::UserInputCount();
nsresult rv = GetResources(&mUserTimeStart, &mSystemTimeStart);
if (NS_FAILED(rv)) {
return false;
}
return true;
@@ -1169,23 +1154,23 @@ nsPerformanceStatsService::StopwatchComm
uint64_t systemTimeDelta = 0;
if (systemTimeStop > mSystemTimeStart)
systemTimeDelta = systemTimeStop - mSystemTimeStart;
MOZ_ASSERT(mTopGroup->isUsedInThisIteration());
const uint64_t totalRecentCycles = mTopGroup->recentCycles(iteration);
- const bool isJankVisible = mIsJankCritical || mozilla::EventStateManager::UserInputCount() > mUserInputCount;
+ 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, isJankVisible, group);
+ 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);
@@ -1193,17 +1178,17 @@ nsPerformanceStatsService::StopwatchComm
return true;
}
void
nsPerformanceStatsService::CommitGroup(uint64_t iteration,
uint64_t totalUserTimeDelta, uint64_t totalSystemTimeDelta,
uint64_t totalCyclesDelta,
- bool isJankVisible,
+ 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();
@@ -1242,17 +1227,19 @@ nsPerformanceStatsService::CommitGroup(u
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);
- group->RecordJankVisibility();
+ if (isHandlingUserInput) {
+ group->RecordUserInput();
+ }
if (totalTimeDelta >= mJankAlertThreshold) {
if (!group->HasPendingAlert()) {
if (mPendingAlerts.append(group)) {
group->SetHasPendingAlert(true);
}
return;
}
@@ -1335,51 +1322,78 @@ nsPerformanceStatsService::GetResources(
*userTime = userTimeInt.QuadPart / 10;
#endif // defined(XP_MACOSX) || defined(XP_UNIX) || defined(XP_WIN)
return NS_OK;
}
void
-nsPerformanceStatsService::NotifyJankObservers() {
+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(nsIPerformanceAlert::REASON_JANK, group);
+ alert = new PerformanceAlert(reason, group);
}
target->NotifyJankObservers(details, alert);
}
- group->ResetHighest();
+ group->ResetRecent();
}
}
NS_IMETHODIMP
nsPerformanceStatsService::GetObservableAddon(const nsAString& addonId,
nsIPerformanceObservable** result) {
if (addonId.Equals(NS_LITERAL_STRING("*"))) {
@@ -1398,16 +1412,42 @@ nsPerformanceStatsService::GetObservable
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
@@ -1437,17 +1477,17 @@ nsPerformanceGroup::nsPerformanceGroup(n
uint64_t processId,
bool isSystem,
GroupScope scope)
: mDetails(new nsPerformanceGroupDetails(name, groupId, addonId, windowId, processId, isSystem))
, mService(service)
, mScope(scope)
, mHighestJank(0)
, mHighestCPOW(0)
- , mIsJankVisible(false)
+ , 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());
@@ -1533,35 +1573,34 @@ nsPerformanceGroup::RecordJank(uint64_t
void
nsPerformanceGroup::RecordCPOW(uint64_t cpow) {
if (cpow > mHighestCPOW) {
mHighestCPOW = cpow;
}
}
-void
-nsPerformanceGroup::RecordJankVisibility() {
- mIsJankVisible = true;
-}
-
uint64_t
nsPerformanceGroup::HighestRecentJank() {
return mHighestJank;
}
uint64_t
nsPerformanceGroup::HighestRecentCPOW() {
return mHighestCPOW;
}
bool
-nsPerformanceGroup::RecentJankVisibility() {
- return mIsJankVisible;
+nsPerformanceGroup::HasRecentUserInput() {
+ return mHasRecentUserInput;
}
void
-nsPerformanceGroup::ResetHighest() {
+nsPerformanceGroup::RecordUserInput() {
+ mHasRecentUserInput = true;
+}
+
+void
+nsPerformanceGroup::ResetRecent() {
mHighestJank = 0;
mHighestCPOW = 0;
- mIsJankVisible = false;
+ mHasRecentUserInput = false;
}
-
--- a/toolkit/components/perfmonitoring/nsPerformanceStats.h
+++ b/toolkit/components/perfmonitoring/nsPerformanceStats.h
@@ -124,27 +124,16 @@ class nsPerformanceStatsService final :
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIPERFORMANCESTATSSERVICE
NS_DECL_NSIOBSERVER
nsPerformanceStatsService();
nsresult Init();
- /**
- * `true` if we are currently in a jank-critical section, i.e. if a
- * slowdown can cause user-visible jank.
- *
- * In the current implementation, we consider that the code is in a
- * jank-critical section if either we are handling user input (see
- * `IsHandlingUserInput()`) or we are running an animation (see
- * `IsAnimating()`).
- */
- static bool IsJankCritical();
-
private:
nsresult InitInternal();
void Dispose();
~nsPerformanceStatsService();
protected:
friend nsPerformanceGroup;
@@ -280,22 +269,17 @@ protected:
* for platforms that don't support per-thread measure) since start.
* Updated by `StopwatchStart` at most once per event.
*
* Unit: microseconds.
*/
uint64_t mUserTimeStart;
uint64_t mSystemTimeStart;
- /**
- * `true` if the iteration currently being examined is jank
- * critical, i.e. if we expect that any slowdown during this
- * iteration will be noticed by the user.
- */
- bool mIsJankCritical;
+ bool mIsHandlingUserInput;
/**
* The number of user inputs since the start of the process. Used to
* determine whether the current iteration has triggered a
* (JS-implemented) user input.
*/
uint64_t mUserInputCount;
@@ -402,37 +386,25 @@ protected:
/**********************************************************
*
* Determining whether jank is user-visible.
*/
/**
- * `true` if we believe that any slowdown can cause an animation to
- * jank, `false` otherwise.
- *
- * In the current implementation, we return `true` iff the vsync
- * driver of the current process has at least one client. This is a
- * pessimistic approximation, insofar as the vsync driver is also
- * used by off-main thread animations, which would not jank in case
- * of slowdown.
- */
- static bool IsAnimating();
-
- /**
* `true` if we believe that any slowdown can cause a noticeable
* delay in handling user-input.
*
* In the current implementation, we return `true` if the latest
* user input was less than MAX_DURATION_OF_INTERACTION_MS ago. This
* includes all inputs (mouse, keyboard, other devices), with the
* exception of mousemove.
*/
- static bool IsHandlingUserInput();
+ bool IsHandlingUserInput();
public:
/**********************************************************
*
* Letting observers register themselves to watch for performance
* alerts.
*
@@ -444,17 +416,17 @@ public:
* firers, we gather all the pending alerts, empty the set and
* dispatch to observers.
*/
/**
* Clear the set of pending alerts and dispatch the pending alerts
* to observers.
*/
- void NotifyJankObservers();
+ void NotifyJankObservers(const mozilla::Vector<uint64_t>& previousJankLevels);
private:
/**
* The set of groups for which we know that an alert should be
* raised. This set is cleared once `mPendingAlertsCollector`
* fires.
*
* Invariant: no group may appear twice in this vector.
@@ -495,16 +467,37 @@ private:
/**
* A buffering delay, in milliseconds, used by the service to
* regroup performance alerts, before observers are actually
* noticed. Higher delays let the system avoid redundant
* notifications for the same group, and are generally better for
* performance.
*/
uint32_t mJankAlertBufferingDelay;
+
+ /**
+ * The threshold above which jank, as reported by the refresh drivers,
+ * is considered user-visible.
+ *
+ * A value of n means that any jank above 2^n ms will be considered
+ * user visible.
+ */
+ short mJankLevelVisibilityThreshold;
+
+ /**
+ * The number of microseconds during which we assume that a
+ * user-interaction can keep the code jank-critical. Any user
+ * interaction that lasts longer than this duration is expected to
+ * either have already caused jank or have caused a nested event
+ * loop.
+ *
+ * In either case, we consider that monitoring
+ * jank-during-interaction after this duration is useless.
+ */
+ uint64_t mMaxExpectedDurationOfInteractionUS;
};
/**
* Container for performance data.
*
* All values are monotonic.
@@ -763,26 +756,28 @@ public:
* Record a CPOW duration.
*
* Update the highest recent CPOW if necessary.
*/
void RecordCPOW(uint64_t cpow);
uint64_t HighestRecentCPOW();
/**
- * Record that any slowdown will be noticed by the user and may
- * therefore need to trigger a slowdown alert.
+ * Record that this group has recently been involved in handling
+ * user input. Note that heuristics are involved here, so the
+ * result is not 100% accurate.
*/
- void RecordJankVisibility();
- bool RecentJankVisibility();
+ void RecordUserInput();
+ bool HasRecentUserInput();
/**
- * Reset highest recent CPOW/jank to 0.
+ * Reset recent values (recent highest CPOW and jank, involvement in
+ * user input).
*/
- void ResetHighest();
+ void ResetRecent();
private:
/**
* The target used by observers to register for watching slow
* performance alerts caused by this group.
*
* May be nullptr for groups that cannot be watched (the top group).
*/
RefPtr<class nsPerformanceObservationTarget> mObservationTarget;
@@ -795,41 +790,30 @@ private:
/**
* The highest CPOW encountered since jank observers for this group
* were last called, in microseconds.
*/
uint64_t mHighestCPOW;
/**
- * `true` if the iteration currently being examined is jank
- * critical, i.e. if we expect that any slowdown during this
- * iteration will be noticed by the user.
+ * `true` if this group has been involved in handling user input,
+ * `false` otherwise.
+ *
+ * Note that we use heuristics to determine whether a group is
+ * involved in handling user input, so this value is not 100%
+ * accurate.
*/
- bool mIsJankVisible;
+ bool mHasRecentUserInput;
/**
* `true` if this group has caused a performance alert and this alert
* hasn't been dispatched yet.
*
* We use this as part of the buffering of performance alerts. If
* the group generates several alerts several times during the
* buffering delay, we only wish to add the group once to the list
* of alerts.
*/
bool mHasPendingAlert;
};
-
-namespace {
- /**
- * The number of milliseconds during which we assume that a
- * user-interaction can keep the code jank-critical. Any user
- * interaction that lasts longer than this duration is expected to
- * either have already caused jank or have caused a nested event
- * loop.
- *
- * In either case, we consider that monitoring
- * jank-during-interaction after this duration is useless.
- */
- const uint64_t MAX_DURATION_OF_INTERACTION_MS = 150;
-}
#endif