diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /dom/base/CCGCScheduler.h | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/base/CCGCScheduler.h')
-rw-r--r-- | dom/base/CCGCScheduler.h | 645 |
1 files changed, 645 insertions, 0 deletions
diff --git a/dom/base/CCGCScheduler.h b/dom/base/CCGCScheduler.h new file mode 100644 index 0000000000..cd2c46eeff --- /dev/null +++ b/dom/base/CCGCScheduler.h @@ -0,0 +1,645 @@ +/* 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 "js/SliceBudget.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/CycleCollectedJSContext.h" +#include "mozilla/MainThreadIdlePeriod.h" +#include "mozilla/Telemetry.h" +#include "mozilla/TimeStamp.h" +#include "nsCycleCollector.h" +#include "nsJSEnvironment.h" + +namespace mozilla { + +static const TimeDuration kOneMinute = TimeDuration::FromSeconds(60.0f); + +// The amount of time we wait between a request to CC (after GC ran) +// and doing the actual CC. +static const TimeDuration kCCDelay = TimeDuration::FromSeconds(6); + +static const TimeDuration kCCSkippableDelay = + TimeDuration::FromMilliseconds(250); + +// In case the cycle collector isn't run at all, we don't want forget skippables +// to run too often. So limit the forget skippable cycle to start at earliest 2 +// seconds after the end of the previous cycle. +static const TimeDuration kTimeBetweenForgetSkippableCycles = + TimeDuration::FromSeconds(2); + +// ForgetSkippable is usually fast, so we can use small budgets. +// This isn't a real budget but a hint to IdleTaskRunner whether there +// is enough time to call ForgetSkippable. +static const TimeDuration kForgetSkippableSliceDuration = + TimeDuration::FromMilliseconds(2); + +// Maximum amount of time that should elapse between incremental CC slices +static const TimeDuration kICCIntersliceDelay = + TimeDuration::FromMilliseconds(64); + +// Time budget for an incremental CC slice when using timer to run it. +static const TimeDuration kICCSliceBudget = TimeDuration::FromMilliseconds(3); +// Minimum budget for an incremental CC slice when using idle time to run it. +static const TimeDuration kIdleICCSliceBudget = + TimeDuration::FromMilliseconds(2); + +// Maximum total duration for an ICC +static const TimeDuration kMaxICCDuration = TimeDuration::FromSeconds(2); + +// Force a CC after this long if there's more than NS_CC_FORCED_PURPLE_LIMIT +// objects in the purple buffer. +static const TimeDuration kCCForced = kOneMinute * 2; +static const uint32_t kCCForcedPurpleLimit = 10; + +// Don't allow an incremental GC to lock out the CC for too long. +static const TimeDuration kMaxCCLockedoutTime = TimeDuration::FromSeconds(30); + +// Trigger a CC if the purple buffer exceeds this size when we check it. +static const uint32_t kCCPurpleLimit = 200; + +enum class CCRunnerAction { + None, + ForgetSkippable, + CleanupContentUnbinder, + CleanupDeferred, + CycleCollect, + StopRunning +}; + +enum CCRunnerYield { Continue, Yield }; + +enum CCRunnerForgetSkippableRemoveChildless { + KeepChildless = false, + RemoveChildless = true +}; + +struct CCRunnerStep { + // The action to scheduler is instructing the caller to perform. + CCRunnerAction mAction; + + // Whether to stop processing actions for this invocation of the timer + // callback. + CCRunnerYield mYield; + + // If the action is ForgetSkippable, then whether to remove childless nodes + // or not. (ForgetSkippable is the only action requiring a parameter; if + // that changes, this will become a union.) + CCRunnerForgetSkippableRemoveChildless mRemoveChildless; +}; + +class CCGCScheduler { + public: + // Mockable functions to interface with the code being scheduled. + + // Current time. In real usage, this will just return TimeStamp::Now(), but + // tests can reimplement it to return a value controlled by the test. + static inline TimeStamp Now(); + + // Number of entries in the purple buffer (those objects whose ref counts + // have been decremented since the previous CC, roughly), and are therefore + // "suspected" of being members of cyclic garbage. + static inline uint32_t SuspectedCCObjects(); + + // Parameter setting + + void SetActiveIntersliceGCBudget(TimeDuration aDuration) { + mActiveIntersliceGCBudget = aDuration; + } + + // State retrieval + + TimeDuration GetCCBlockedTime(TimeStamp aNow) const { + MOZ_ASSERT(mInIncrementalGC); + MOZ_ASSERT(!mCCBlockStart.IsNull()); + return aNow - mCCBlockStart; + } + + bool InIncrementalGC() const { return mInIncrementalGC; } + + TimeStamp GetLastCCEndTime() const { return mLastCCEndTime; } + + bool IsEarlyForgetSkippable(uint32_t aN = kMajorForgetSkippableCalls) const { + return mCleanupsSinceLastGC < aN; + } + + bool NeedsFullGC() const { return mNeedsFullGC; } + + // State modification + + void SetNeedsFullGC(bool aNeedGC = true) { mNeedsFullGC = aNeedGC; } + + // Ensure that the current runner does a cycle collection, and trigger a GC + // after it finishes. + void EnsureCCThenGC() { + MOZ_ASSERT(mCCRunnerState != CCRunnerState::Inactive); + mNeedsFullCC = true; + mNeedsGCAfterCC = true; + } + + void NoteGCBegin() { + // Treat all GC as incremental here; non-incremental GC will just appear to + // be one slice. + mInIncrementalGC = true; + } + + void NoteGCEnd() { + mInIncrementalGC = false; + mCCBlockStart = TimeStamp(); + mInIncrementalGC = false; + mNeedsFullCC = true; + mHasRunGC = true; + + mCleanupsSinceLastGC = 0; + mCCollectedWaitingForGC = 0; + mCCollectedZonesWaitingForGC = 0; + mLikelyShortLivingObjectsNeedingGC = 0; + } + + // When we decide to do a cycle collection but we're in the middle of an + // incremental GC, the CC is "locked out" until the GC completes -- unless + // the wait is too long, and we decide to finish the incremental GC early. + void BlockCC(TimeStamp aNow) { + MOZ_ASSERT(mInIncrementalGC); + MOZ_ASSERT(mCCBlockStart.IsNull()); + mCCBlockStart = aNow; + } + + void UnblockCC() { mCCBlockStart = TimeStamp(); } + + // Returns the number of purple buffer items that were processed and removed. + uint32_t NoteForgetSkippableComplete( + TimeStamp aNow, uint32_t aSuspectedBeforeForgetSkippable) { + mLastForgetSkippableEndTime = aNow; + uint32_t suspected = SuspectedCCObjects(); + mPreviousSuspectedCount = suspected; + mCleanupsSinceLastGC++; + return aSuspectedBeforeForgetSkippable - suspected; + } + + // After collecting cycles, record the results that are used in scheduling + // decisions. + void NoteCycleCollected(const CycleCollectorResults& aResults) { + mCCollectedWaitingForGC += aResults.mFreedGCed; + mCCollectedZonesWaitingForGC += aResults.mFreedJSZones; + } + + // This is invoked when the whole process of collection is done -- i.e., CC + // preparation (eg ForgetSkippables), the CC itself, and the optional + // followup GC. There really ought to be a separate name for the overall CC + // as opposed to the actual cycle collection portion. + void NoteCCEnd(TimeStamp aWhen) { + mLastCCEndTime = aWhen; + mNeedsFullCC = false; + + // The GC for this CC has already been requested. + mNeedsGCAfterCC = false; + } + + // The CC was abandoned without running a slice, so we only did forget + // skippables. Prevent running another cycle soon. + void NoteForgetSkippableOnlyCycle() { + mLastForgetSkippableCycleEndTime = Now(); + } + + void Shutdown() { mDidShutdown = true; } + + // Scheduling + + // Return a budget along with a boolean saying whether to prefer to run short + // slices and stop rather than continuing to the next phase of cycle + // collection. + inline js::SliceBudget ComputeCCSliceBudget(TimeStamp aDeadline, + TimeStamp aCCBeginTime, + TimeStamp aPrevSliceEndTime, + bool* aPreferShorterSlices) const; + + inline TimeDuration ComputeInterSliceGCBudget(TimeStamp aDeadline, + TimeStamp aNow) const; + + bool ShouldForgetSkippable() const { + // Only do a forget skippable if there are more than a few new objects + // or we're doing the initial forget skippables. + return ((mPreviousSuspectedCount + 100) <= SuspectedCCObjects()) || + mCleanupsSinceLastGC < kMajorForgetSkippableCalls; + } + + // There is reason to suspect that there may be a significant amount of + // garbage to cycle collect: either we just finished a GC, or the purple + // buffer is getting really big, or it's getting somewhat big and it has been + // too long since the last CC. + bool IsCCNeeded(TimeStamp aNow = Now()) const { + if (mNeedsFullCC) { + return true; + } + uint32_t suspected = SuspectedCCObjects(); + return suspected > kCCPurpleLimit || + (suspected > kCCForcedPurpleLimit && mLastCCEndTime && + aNow - mLastCCEndTime > kCCForced); + } + + inline bool ShouldScheduleCC() const; + + // If we collected a substantial amount of cycles, poke the GC since more + // objects might be unreachable now. + bool NeedsGCAfterCC() const { + return mCCollectedWaitingForGC > 250 || mCCollectedZonesWaitingForGC > 0 || + mLikelyShortLivingObjectsNeedingGC > 2500 || mNeedsGCAfterCC; + } + + bool IsLastEarlyCCTimer(int32_t aCurrentFireCount) const { + int32_t numEarlyTimerFires = + std::max(int32_t(mCCDelay / kCCSkippableDelay) - 2, 1); + + return aCurrentFireCount >= numEarlyTimerFires; + } + + enum class CCRunnerState { + Inactive, + ReducePurple, + CleanupChildless, + CleanupContentUnbinder, + CleanupDeferred, + StartCycleCollection, + CycleCollecting, + Canceled, + NumStates + }; + + void InitCCRunnerStateMachine(CCRunnerState initialState) { + // The state machine should always have been deactivated after the previous + // collection, however far that collection may have gone. + MOZ_ASSERT(mCCRunnerState == CCRunnerState::Inactive, + "DeactivateCCRunner should have been called"); + mCCRunnerState = initialState; + + // Currently, there are only two entry points to the non-Inactive part of + // the state machine. + if (initialState == CCRunnerState::ReducePurple) { + mCCDelay = kCCDelay; + mCCRunnerEarlyFireCount = 0; + } else if (initialState == CCRunnerState::CycleCollecting) { + // Nothing needed. + } else { + MOZ_CRASH("Invalid initial state"); + } + } + + void DeactivateCCRunner() { mCCRunnerState = CCRunnerState::Inactive; } + + inline CCRunnerStep GetNextCCRunnerAction(TimeStamp aDeadline); + + // aStartTimeStamp : when the ForgetSkippable timer fired. This may be some + // time ago, if an incremental GC needed to be finished. + js::SliceBudget ComputeForgetSkippableBudget(TimeStamp aStartTimeStamp, + TimeStamp aDeadline); + + private: + // State + + // An incremental GC is in progress, which blocks the CC from running for its + // duration (or until it goes too long and is finished synchronously.) + bool mInIncrementalGC = false; + + // When the CC started actually waiting for the GC to finish. This will be + // set to non-null at a later time than mCCLockedOut. + TimeStamp mCCBlockStart; + + bool mDidShutdown = false; + + TimeStamp mLastForgetSkippableEndTime; + uint32_t mForgetSkippableCounter = 0; + TimeStamp mForgetSkippableFrequencyStartTime; + TimeStamp mLastCCEndTime; + TimeStamp mLastForgetSkippableCycleEndTime; + + CCRunnerState mCCRunnerState = CCRunnerState::Inactive; + int32_t mCCRunnerEarlyFireCount = 0; + TimeDuration mCCDelay = kCCDelay; + + // Prevent the very first CC from running before we have GC'd and set the + // gray bits. + bool mHasRunGC = false; + + bool mNeedsFullCC = false; + bool mNeedsFullGC = true; + bool mNeedsGCAfterCC = false; + uint32_t mPreviousSuspectedCount = 0; + + uint32_t mCleanupsSinceLastGC = UINT32_MAX; + + public: + uint32_t mCCollectedWaitingForGC = 0; + uint32_t mCCollectedZonesWaitingForGC = 0; + uint32_t mLikelyShortLivingObjectsNeedingGC = 0; + + // Configuration parameters + + TimeDuration mActiveIntersliceGCBudget = TimeDuration::FromMilliseconds(5); +}; + +js::SliceBudget CCGCScheduler::ComputeCCSliceBudget( + TimeStamp aDeadline, TimeStamp aCCBeginTime, TimeStamp aPrevSliceEndTime, + bool* aPreferShorterSlices) const { + TimeStamp now = Now(); + + *aPreferShorterSlices = + aDeadline.IsNull() || (aDeadline - now) < kICCSliceBudget; + + TimeDuration baseBudget = + aDeadline.IsNull() ? kICCSliceBudget : aDeadline - now; + + if (aCCBeginTime.IsNull()) { + // If no CC is in progress, use the standard slice time. + return js::SliceBudget(baseBudget); + } + + // Only run a limited slice if we're within the max running time. + MOZ_ASSERT(now >= aCCBeginTime); + TimeDuration runningTime = now - aCCBeginTime; + if (runningTime >= kMaxICCDuration) { + return js::SliceBudget::unlimited(); + } + + const TimeDuration maxSlice = + TimeDuration::FromMilliseconds(MainThreadIdlePeriod::GetLongIdlePeriod()); + + // Try to make up for a delay in running this slice. + MOZ_ASSERT(now >= aPrevSliceEndTime); + double sliceDelayMultiplier = (now - aPrevSliceEndTime) / kICCIntersliceDelay; + TimeDuration delaySliceBudget = + std::min(baseBudget.MultDouble(sliceDelayMultiplier), maxSlice); + + // Increase slice budgets up to |maxSlice| as we approach + // half way through the ICC, to avoid large sync CCs. + double percentToHalfDone = + std::min(2.0 * (runningTime / kMaxICCDuration), 1.0); + TimeDuration laterSliceBudget = maxSlice.MultDouble(percentToHalfDone); + + // Note: We may have already overshot the deadline, in which case + // baseBudget will be negative and we will end up returning + // laterSliceBudget. + return js::SliceBudget( + std::max({delaySliceBudget, laterSliceBudget, baseBudget})); +} + +inline TimeDuration CCGCScheduler::ComputeInterSliceGCBudget( + TimeStamp aDeadline, TimeStamp aNow) const { + // We use longer budgets when the CC has been locked out but the CC has + // tried to run since that means we may have a significant amount of + // garbage to collect and it's better to GC in several longer slices than + // in a very long one. + TimeDuration budget = + aDeadline.IsNull() ? mActiveIntersliceGCBudget * 2 : aDeadline - aNow; + if (!mCCBlockStart) { + return budget; + } + + TimeDuration blockedTime = aNow - mCCBlockStart; + TimeDuration maxSliceGCBudget = mActiveIntersliceGCBudget * 10; + double percentOfBlockedTime = + std::min(blockedTime / kMaxCCLockedoutTime, 1.0); + return std::max(budget, maxSliceGCBudget.MultDouble(percentOfBlockedTime)); +} + +bool CCGCScheduler::ShouldScheduleCC() const { + if (!mHasRunGC) { + return false; + } + + TimeStamp now = Now(); + + // Don't run consecutive CCs too often. + if (mCleanupsSinceLastGC && !mLastCCEndTime.IsNull()) { + if (now - mLastCCEndTime < kCCDelay) { + return false; + } + } + + // If GC hasn't run recently and forget skippable only cycle was run, + // don't start a new cycle too soon. + if ((mCleanupsSinceLastGC > kMajorForgetSkippableCalls) && + !mLastForgetSkippableCycleEndTime.IsNull()) { + if (now - mLastForgetSkippableCycleEndTime < + kTimeBetweenForgetSkippableCycles) { + return false; + } + } + + return IsCCNeeded(now); +} + +CCRunnerStep CCGCScheduler::GetNextCCRunnerAction(TimeStamp aDeadline) { + struct StateDescriptor { + // When in this state, should we first check to see if we still have + // enough reason to CC? + bool mCanAbortCC; + + // If we do decide to abort the CC, should we still try to forget + // skippables one more time? + bool mTryFinalForgetSkippable; + }; + + // The state descriptors for Inactive and Canceled will never actually be + // used. We will never call this function while Inactive, and Canceled is + // handled specially at the beginning. + constexpr StateDescriptor stateDescriptors[] = { + {false, false}, /* CCRunnerState::Inactive */ + {false, false}, /* CCRunnerState::ReducePurple */ + {true, true}, /* CCRunnerState::CleanupChildless */ + {true, false}, /* CCRunnerState::CleanupContentUnbinder */ + {false, false}, /* CCRunnerState::CleanupDeferred */ + {false, false}, /* CCRunnerState::StartCycleCollection */ + {false, false}, /* CCRunnerState::CycleCollecting */ + {false, false}}; /* CCRunnerState::Canceled */ + static_assert( + ArrayLength(stateDescriptors) == size_t(CCRunnerState::NumStates), + "need one state descriptor per state"); + const StateDescriptor& desc = stateDescriptors[int(mCCRunnerState)]; + + // Make sure we initialized the state machine. + MOZ_ASSERT(mCCRunnerState != CCRunnerState::Inactive); + + if (mDidShutdown) { + return {CCRunnerAction::StopRunning, Yield}; + } + + if (mCCRunnerState == CCRunnerState::Canceled) { + // When we cancel a cycle, there may have been a final ForgetSkippable. + return {CCRunnerAction::StopRunning, Yield}; + } + + TimeStamp now = Now(); + + if (InIncrementalGC()) { + if (mCCBlockStart.IsNull()) { + BlockCC(now); + + // If we have reached the CycleCollecting state, then ignore CC timer + // fires while incremental GC is running. (Running ICC during an IGC + // would cause us to synchronously finish the GC, which is bad.) + // + // If we have not yet started cycle collecting, then reset our state so + // that we run forgetSkippable often enough before CC. Because of reduced + // mCCDelay, forgetSkippable will be called just a few times. + // + // The kMaxCCLockedoutTime limit guarantees that we end up calling + // forgetSkippable and CycleCollectNow eventually. + + if (mCCRunnerState != CCRunnerState::CycleCollecting) { + mCCRunnerState = CCRunnerState::ReducePurple; + mCCRunnerEarlyFireCount = 0; + mCCDelay = kCCDelay / int64_t(3); + } + return {CCRunnerAction::None, Yield}; + } + + if (GetCCBlockedTime(now) < kMaxCCLockedoutTime) { + return {CCRunnerAction::None, Yield}; + } + + // Locked out for too long, so proceed and finish the incremental GC + // synchronously. + } + + // For states that aren't just continuations of previous states, check + // whether a CC is still needed (after doing various things to reduce the + // purple buffer). + if (desc.mCanAbortCC && !IsCCNeeded(now)) { + // If we don't pass the threshold for wanting to cycle collect, stop now + // (after possibly doing a final ForgetSkippable). + mCCRunnerState = CCRunnerState::Canceled; + NoteForgetSkippableOnlyCycle(); + + // Preserve the previous code's idea of when to check whether a + // ForgetSkippable should be fired. + if (desc.mTryFinalForgetSkippable && ShouldForgetSkippable()) { + // The Canceled state will make us StopRunning after this action is + // performed (see conditional at top of function). + return {CCRunnerAction::ForgetSkippable, Yield, KeepChildless}; + } + + return {CCRunnerAction::StopRunning, Yield}; + } + + switch (mCCRunnerState) { + // ReducePurple: a GC ran (or we otherwise decided to try CC'ing). Wait + // for some amount of time (kCCDelay, or less if incremental GC blocked + // this CC) while firing regular ForgetSkippable actions before continuing + // on. + case CCRunnerState::ReducePurple: + ++mCCRunnerEarlyFireCount; + if (IsLastEarlyCCTimer(mCCRunnerEarlyFireCount)) { + mCCRunnerState = CCRunnerState::CleanupChildless; + } + + if (ShouldForgetSkippable()) { + return {CCRunnerAction::ForgetSkippable, Yield, KeepChildless}; + } + + if (aDeadline.IsNull()) { + return {CCRunnerAction::None, Yield}; + } + + // If we're called during idle time, try to find some work to do by + // advancing to the next state, effectively bypassing some possible forget + // skippable calls. + mCCRunnerState = CCRunnerState::CleanupChildless; + + // Continue on to CleanupChildless, but only after checking IsCCNeeded + // again. + return {CCRunnerAction::None, Continue}; + + // CleanupChildless: do a stronger ForgetSkippable that removes nodes with + // no children in the cycle collector graph. This state is split into 3 + // parts; the other Cleanup* actions will happen within the same callback + // (unless the ForgetSkippable shrinks the purple buffer enough for the CC + // to be skipped entirely.) + case CCRunnerState::CleanupChildless: + mCCRunnerState = CCRunnerState::CleanupContentUnbinder; + return {CCRunnerAction::ForgetSkippable, Yield, RemoveChildless}; + + // CleanupContentUnbinder: continuing cleanup, clear out the content + // unbinder. + case CCRunnerState::CleanupContentUnbinder: + if (aDeadline.IsNull()) { + // Non-idle (waiting) callbacks skip the rest of the cleanup, but still + // wait for another fire before the actual CC. + mCCRunnerState = CCRunnerState::StartCycleCollection; + return {CCRunnerAction::None, Yield}; + } + + // Running in an idle callback. + + // The deadline passed, so go straight to CC in the next slice. + if (now >= aDeadline) { + mCCRunnerState = CCRunnerState::StartCycleCollection; + return {CCRunnerAction::None, Yield}; + } + + mCCRunnerState = CCRunnerState::CleanupDeferred; + return {CCRunnerAction::CleanupContentUnbinder, Continue}; + + // CleanupDeferred: continuing cleanup, do deferred deletion. + case CCRunnerState::CleanupDeferred: + MOZ_ASSERT(!aDeadline.IsNull(), + "Should only be in CleanupDeferred state when idle"); + + // Our efforts to avoid a CC have failed. Let the timer fire once more + // to trigger a CC. + mCCRunnerState = CCRunnerState::StartCycleCollection; + if (now >= aDeadline) { + // The deadline passed, go straight to CC in the next slice. + return {CCRunnerAction::None, Yield}; + } + + return {CCRunnerAction::CleanupDeferred, Yield}; + + // StartCycleCollection: start actually doing cycle collection slices. + case CCRunnerState::StartCycleCollection: + // We are in the final timer fire and still meet the conditions for + // triggering a CC. Let RunCycleCollectorSlice finish the current IGC if + // any, because that will allow us to include the GC time in the CC pause. + mCCRunnerState = CCRunnerState::CycleCollecting; + [[fallthrough]]; + + // CycleCollecting: continue running slices until done. + case CCRunnerState::CycleCollecting: + return {CCRunnerAction::CycleCollect, Yield}; + + default: + MOZ_CRASH("Unexpected CCRunner state"); + }; +} + +inline js::SliceBudget CCGCScheduler::ComputeForgetSkippableBudget( + TimeStamp aStartTimeStamp, TimeStamp aDeadline) { + if (mForgetSkippableFrequencyStartTime.IsNull()) { + mForgetSkippableFrequencyStartTime = aStartTimeStamp; + } else if (aStartTimeStamp - mForgetSkippableFrequencyStartTime > + kOneMinute) { + TimeStamp startPlusMinute = mForgetSkippableFrequencyStartTime + kOneMinute; + + // If we had forget skippables only at the beginning of the interval, we + // still want to use the whole time, minute or more, for frequency + // calculation. mLastForgetSkippableEndTime is needed if forget skippable + // takes enough time to push the interval to be over a minute. + TimeStamp endPoint = std::max(startPlusMinute, mLastForgetSkippableEndTime); + + // Duration in minutes. + double duration = + (endPoint - mForgetSkippableFrequencyStartTime).ToSeconds() / 60; + uint32_t frequencyPerMinute = uint32_t(mForgetSkippableCounter / duration); + Telemetry::Accumulate(Telemetry::FORGET_SKIPPABLE_FREQUENCY, + frequencyPerMinute); + mForgetSkippableCounter = 0; + mForgetSkippableFrequencyStartTime = aStartTimeStamp; + } + ++mForgetSkippableCounter; + + TimeDuration budgetTime = + aDeadline ? (aDeadline - aStartTimeStamp) : kForgetSkippableSliceDuration; + return js::SliceBudget(budgetTime); +} + +} // namespace mozilla |