1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "js/SliceBudget.h"
6 #include "mozilla/ArrayUtils.h"
7 #include "mozilla/CycleCollectedJSContext.h"
8 #include "mozilla/MainThreadIdlePeriod.h"
9 #include "mozilla/Telemetry.h"
10 #include "mozilla/TimeStamp.h"
11 #include "nsCycleCollector.h"
12 #include "nsJSEnvironment.h"
16 static const TimeDuration kOneMinute
= TimeDuration::FromSeconds(60.0f
);
18 // The amount of time we wait between a request to CC (after GC ran)
19 // and doing the actual CC.
20 static const TimeDuration kCCDelay
= TimeDuration::FromSeconds(6);
22 static const TimeDuration kCCSkippableDelay
=
23 TimeDuration::FromMilliseconds(250);
25 // In case the cycle collector isn't run at all, we don't want forget skippables
26 // to run too often. So limit the forget skippable cycle to start at earliest 2
27 // seconds after the end of the previous cycle.
28 static const TimeDuration kTimeBetweenForgetSkippableCycles
=
29 TimeDuration::FromSeconds(2);
31 // ForgetSkippable is usually fast, so we can use small budgets.
32 // This isn't a real budget but a hint to IdleTaskRunner whether there
33 // is enough time to call ForgetSkippable.
34 static const TimeDuration kForgetSkippableSliceDuration
=
35 TimeDuration::FromMilliseconds(2);
37 // Maximum amount of time that should elapse between incremental CC slices
38 static const TimeDuration kICCIntersliceDelay
=
39 TimeDuration::FromMilliseconds(64);
41 // Time budget for an incremental CC slice when using timer to run it.
42 static const TimeDuration kICCSliceBudget
= TimeDuration::FromMilliseconds(3);
43 // Minimum budget for an incremental CC slice when using idle time to run it.
44 static const TimeDuration kIdleICCSliceBudget
=
45 TimeDuration::FromMilliseconds(2);
47 // Maximum total duration for an ICC
48 static const TimeDuration kMaxICCDuration
= TimeDuration::FromSeconds(2);
50 // Force a CC after this long if there's more than NS_CC_FORCED_PURPLE_LIMIT
51 // objects in the purple buffer.
52 static const TimeDuration kCCForced
= kOneMinute
* 2;
53 static const uint32_t kCCForcedPurpleLimit
= 10;
55 // Don't allow an incremental GC to lock out the CC for too long.
56 static const TimeDuration kMaxCCLockedoutTime
= TimeDuration::FromSeconds(30);
58 // Trigger a CC if the purple buffer exceeds this size when we check it.
59 static const uint32_t kCCPurpleLimit
= 200;
61 enum class CCRunnerAction
{
64 CleanupContentUnbinder
,
70 enum CCRunnerYield
{ Continue
, Yield
};
72 enum CCRunnerForgetSkippableRemoveChildless
{
73 KeepChildless
= false,
74 RemoveChildless
= true
78 // The action to scheduler is instructing the caller to perform.
79 CCRunnerAction mAction
;
81 // Whether to stop processing actions for this invocation of the timer
85 // If the action is ForgetSkippable, then whether to remove childless nodes
86 // or not. (ForgetSkippable is the only action requiring a parameter; if
87 // that changes, this will become a union.)
88 CCRunnerForgetSkippableRemoveChildless mRemoveChildless
;
93 // Mockable functions to interface with the code being scheduled.
95 // Current time. In real usage, this will just return TimeStamp::Now(), but
96 // tests can reimplement it to return a value controlled by the test.
97 static inline TimeStamp
Now();
99 // Number of entries in the purple buffer (those objects whose ref counts
100 // have been decremented since the previous CC, roughly), and are therefore
101 // "suspected" of being members of cyclic garbage.
102 static inline uint32_t SuspectedCCObjects();
106 void SetActiveIntersliceGCBudget(TimeDuration aDuration
) {
107 mActiveIntersliceGCBudget
= aDuration
;
112 TimeDuration
GetCCBlockedTime(TimeStamp aNow
) const {
113 MOZ_ASSERT(mInIncrementalGC
);
114 MOZ_ASSERT(!mCCBlockStart
.IsNull());
115 return aNow
- mCCBlockStart
;
118 bool InIncrementalGC() const { return mInIncrementalGC
; }
120 TimeStamp
GetLastCCEndTime() const { return mLastCCEndTime
; }
122 bool IsEarlyForgetSkippable(uint32_t aN
= kMajorForgetSkippableCalls
) const {
123 return mCleanupsSinceLastGC
< aN
;
126 bool NeedsFullGC() const { return mNeedsFullGC
; }
128 // State modification
130 void SetNeedsFullGC(bool aNeedGC
= true) { mNeedsFullGC
= aNeedGC
; }
132 // Ensure that the current runner does a cycle collection, and trigger a GC
133 // after it finishes.
134 void EnsureCCThenGC() {
135 MOZ_ASSERT(mCCRunnerState
!= CCRunnerState::Inactive
);
137 mNeedsGCAfterCC
= true;
141 // Treat all GC as incremental here; non-incremental GC will just appear to
143 mInIncrementalGC
= true;
147 mInIncrementalGC
= false;
148 mCCBlockStart
= TimeStamp();
149 mInIncrementalGC
= false;
153 mCleanupsSinceLastGC
= 0;
154 mCCollectedWaitingForGC
= 0;
155 mCCollectedZonesWaitingForGC
= 0;
156 mLikelyShortLivingObjectsNeedingGC
= 0;
159 // When we decide to do a cycle collection but we're in the middle of an
160 // incremental GC, the CC is "locked out" until the GC completes -- unless
161 // the wait is too long, and we decide to finish the incremental GC early.
162 void BlockCC(TimeStamp aNow
) {
163 MOZ_ASSERT(mInIncrementalGC
);
164 MOZ_ASSERT(mCCBlockStart
.IsNull());
165 mCCBlockStart
= aNow
;
168 void UnblockCC() { mCCBlockStart
= TimeStamp(); }
170 // Returns the number of purple buffer items that were processed and removed.
171 uint32_t NoteForgetSkippableComplete(
172 TimeStamp aNow
, uint32_t aSuspectedBeforeForgetSkippable
) {
173 mLastForgetSkippableEndTime
= aNow
;
174 uint32_t suspected
= SuspectedCCObjects();
175 mPreviousSuspectedCount
= suspected
;
176 mCleanupsSinceLastGC
++;
177 return aSuspectedBeforeForgetSkippable
- suspected
;
180 // After collecting cycles, record the results that are used in scheduling
182 void NoteCycleCollected(const CycleCollectorResults
& aResults
) {
183 mCCollectedWaitingForGC
+= aResults
.mFreedGCed
;
184 mCCollectedZonesWaitingForGC
+= aResults
.mFreedJSZones
;
187 // This is invoked when the whole process of collection is done -- i.e., CC
188 // preparation (eg ForgetSkippables), the CC itself, and the optional
189 // followup GC. There really ought to be a separate name for the overall CC
190 // as opposed to the actual cycle collection portion.
191 void NoteCCEnd(TimeStamp aWhen
) {
192 mLastCCEndTime
= aWhen
;
193 mNeedsFullCC
= false;
195 // The GC for this CC has already been requested.
196 mNeedsGCAfterCC
= false;
199 // The CC was abandoned without running a slice, so we only did forget
200 // skippables. Prevent running another cycle soon.
201 void NoteForgetSkippableOnlyCycle() {
202 mLastForgetSkippableCycleEndTime
= Now();
205 void Shutdown() { mDidShutdown
= true; }
209 // Return a budget along with a boolean saying whether to prefer to run short
210 // slices and stop rather than continuing to the next phase of cycle
212 inline js::SliceBudget
ComputeCCSliceBudget(TimeStamp aDeadline
,
213 TimeStamp aCCBeginTime
,
214 TimeStamp aPrevSliceEndTime
,
215 bool* aPreferShorterSlices
) const;
217 inline TimeDuration
ComputeInterSliceGCBudget(TimeStamp aDeadline
,
218 TimeStamp aNow
) const;
220 bool ShouldForgetSkippable() const {
221 // Only do a forget skippable if there are more than a few new objects
222 // or we're doing the initial forget skippables.
223 return ((mPreviousSuspectedCount
+ 100) <= SuspectedCCObjects()) ||
224 mCleanupsSinceLastGC
< kMajorForgetSkippableCalls
;
227 // There is reason to suspect that there may be a significant amount of
228 // garbage to cycle collect: either we just finished a GC, or the purple
229 // buffer is getting really big, or it's getting somewhat big and it has been
230 // too long since the last CC.
231 bool IsCCNeeded(TimeStamp aNow
= Now()) const {
235 uint32_t suspected
= SuspectedCCObjects();
236 return suspected
> kCCPurpleLimit
||
237 (suspected
> kCCForcedPurpleLimit
&& mLastCCEndTime
&&
238 aNow
- mLastCCEndTime
> kCCForced
);
241 inline bool ShouldScheduleCC() const;
243 // If we collected a substantial amount of cycles, poke the GC since more
244 // objects might be unreachable now.
245 bool NeedsGCAfterCC() const {
246 return mCCollectedWaitingForGC
> 250 || mCCollectedZonesWaitingForGC
> 0 ||
247 mLikelyShortLivingObjectsNeedingGC
> 2500 || mNeedsGCAfterCC
;
250 bool IsLastEarlyCCTimer(int32_t aCurrentFireCount
) const {
251 int32_t numEarlyTimerFires
=
252 std::max(int32_t(mCCDelay
/ kCCSkippableDelay
) - 2, 1);
254 return aCurrentFireCount
>= numEarlyTimerFires
;
257 enum class CCRunnerState
{
261 CleanupContentUnbinder
,
263 StartCycleCollection
,
269 void InitCCRunnerStateMachine(CCRunnerState initialState
) {
270 // The state machine should always have been deactivated after the previous
271 // collection, however far that collection may have gone.
272 MOZ_ASSERT(mCCRunnerState
== CCRunnerState::Inactive
,
273 "DeactivateCCRunner should have been called");
274 mCCRunnerState
= initialState
;
276 // Currently, there are only two entry points to the non-Inactive part of
277 // the state machine.
278 if (initialState
== CCRunnerState::ReducePurple
) {
280 mCCRunnerEarlyFireCount
= 0;
281 } else if (initialState
== CCRunnerState::CycleCollecting
) {
284 MOZ_CRASH("Invalid initial state");
288 void DeactivateCCRunner() { mCCRunnerState
= CCRunnerState::Inactive
; }
290 inline CCRunnerStep
GetNextCCRunnerAction(TimeStamp aDeadline
);
292 // aStartTimeStamp : when the ForgetSkippable timer fired. This may be some
293 // time ago, if an incremental GC needed to be finished.
294 js::SliceBudget
ComputeForgetSkippableBudget(TimeStamp aStartTimeStamp
,
295 TimeStamp aDeadline
);
300 // An incremental GC is in progress, which blocks the CC from running for its
301 // duration (or until it goes too long and is finished synchronously.)
302 bool mInIncrementalGC
= false;
304 // When the CC started actually waiting for the GC to finish. This will be
305 // set to non-null at a later time than mCCLockedOut.
306 TimeStamp mCCBlockStart
;
308 bool mDidShutdown
= false;
310 TimeStamp mLastForgetSkippableEndTime
;
311 uint32_t mForgetSkippableCounter
= 0;
312 TimeStamp mForgetSkippableFrequencyStartTime
;
313 TimeStamp mLastCCEndTime
;
314 TimeStamp mLastForgetSkippableCycleEndTime
;
316 CCRunnerState mCCRunnerState
= CCRunnerState::Inactive
;
317 int32_t mCCRunnerEarlyFireCount
= 0;
318 TimeDuration mCCDelay
= kCCDelay
;
320 // Prevent the very first CC from running before we have GC'd and set the
322 bool mHasRunGC
= false;
324 bool mNeedsFullCC
= false;
325 bool mNeedsFullGC
= true;
326 bool mNeedsGCAfterCC
= false;
327 uint32_t mPreviousSuspectedCount
= 0;
329 uint32_t mCleanupsSinceLastGC
= UINT32_MAX
;
332 uint32_t mCCollectedWaitingForGC
= 0;
333 uint32_t mCCollectedZonesWaitingForGC
= 0;
334 uint32_t mLikelyShortLivingObjectsNeedingGC
= 0;
336 // Configuration parameters
338 TimeDuration mActiveIntersliceGCBudget
= TimeDuration::FromMilliseconds(5);
341 js::SliceBudget
CCGCScheduler::ComputeCCSliceBudget(
342 TimeStamp aDeadline
, TimeStamp aCCBeginTime
, TimeStamp aPrevSliceEndTime
,
343 bool* aPreferShorterSlices
) const {
344 TimeStamp now
= Now();
346 *aPreferShorterSlices
=
347 aDeadline
.IsNull() || (aDeadline
- now
) < kICCSliceBudget
;
349 TimeDuration baseBudget
=
350 aDeadline
.IsNull() ? kICCSliceBudget
: aDeadline
- now
;
352 if (aCCBeginTime
.IsNull()) {
353 // If no CC is in progress, use the standard slice time.
354 return js::SliceBudget(baseBudget
);
357 // Only run a limited slice if we're within the max running time.
358 MOZ_ASSERT(now
>= aCCBeginTime
);
359 TimeDuration runningTime
= now
- aCCBeginTime
;
360 if (runningTime
>= kMaxICCDuration
) {
361 return js::SliceBudget::unlimited();
364 const TimeDuration maxSlice
=
365 TimeDuration::FromMilliseconds(MainThreadIdlePeriod::GetLongIdlePeriod());
367 // Try to make up for a delay in running this slice.
368 MOZ_ASSERT(now
>= aPrevSliceEndTime
);
369 double sliceDelayMultiplier
= (now
- aPrevSliceEndTime
) / kICCIntersliceDelay
;
370 TimeDuration delaySliceBudget
=
371 std::min(baseBudget
.MultDouble(sliceDelayMultiplier
), maxSlice
);
373 // Increase slice budgets up to |maxSlice| as we approach
374 // half way through the ICC, to avoid large sync CCs.
375 double percentToHalfDone
=
376 std::min(2.0 * (runningTime
/ kMaxICCDuration
), 1.0);
377 TimeDuration laterSliceBudget
= maxSlice
.MultDouble(percentToHalfDone
);
379 // Note: We may have already overshot the deadline, in which case
380 // baseBudget will be negative and we will end up returning
382 return js::SliceBudget(
383 std::max({delaySliceBudget
, laterSliceBudget
, baseBudget
}));
386 inline TimeDuration
CCGCScheduler::ComputeInterSliceGCBudget(
387 TimeStamp aDeadline
, TimeStamp aNow
) const {
388 // We use longer budgets when the CC has been locked out but the CC has
389 // tried to run since that means we may have a significant amount of
390 // garbage to collect and it's better to GC in several longer slices than
391 // in a very long one.
392 TimeDuration budget
=
393 aDeadline
.IsNull() ? mActiveIntersliceGCBudget
* 2 : aDeadline
- aNow
;
394 if (!mCCBlockStart
) {
398 TimeDuration blockedTime
= aNow
- mCCBlockStart
;
399 TimeDuration maxSliceGCBudget
= mActiveIntersliceGCBudget
* 10;
400 double percentOfBlockedTime
=
401 std::min(blockedTime
/ kMaxCCLockedoutTime
, 1.0);
402 return std::max(budget
, maxSliceGCBudget
.MultDouble(percentOfBlockedTime
));
405 bool CCGCScheduler::ShouldScheduleCC() const {
410 TimeStamp now
= Now();
412 // Don't run consecutive CCs too often.
413 if (mCleanupsSinceLastGC
&& !mLastCCEndTime
.IsNull()) {
414 if (now
- mLastCCEndTime
< kCCDelay
) {
419 // If GC hasn't run recently and forget skippable only cycle was run,
420 // don't start a new cycle too soon.
421 if ((mCleanupsSinceLastGC
> kMajorForgetSkippableCalls
) &&
422 !mLastForgetSkippableCycleEndTime
.IsNull()) {
423 if (now
- mLastForgetSkippableCycleEndTime
<
424 kTimeBetweenForgetSkippableCycles
) {
429 return IsCCNeeded(now
);
432 CCRunnerStep
CCGCScheduler::GetNextCCRunnerAction(TimeStamp aDeadline
) {
433 struct StateDescriptor
{
434 // When in this state, should we first check to see if we still have
435 // enough reason to CC?
438 // If we do decide to abort the CC, should we still try to forget
439 // skippables one more time?
440 bool mTryFinalForgetSkippable
;
443 // The state descriptors for Inactive and Canceled will never actually be
444 // used. We will never call this function while Inactive, and Canceled is
445 // handled specially at the beginning.
446 constexpr StateDescriptor stateDescriptors
[] = {
447 {false, false}, /* CCRunnerState::Inactive */
448 {false, false}, /* CCRunnerState::ReducePurple */
449 {true, true}, /* CCRunnerState::CleanupChildless */
450 {true, false}, /* CCRunnerState::CleanupContentUnbinder */
451 {false, false}, /* CCRunnerState::CleanupDeferred */
452 {false, false}, /* CCRunnerState::StartCycleCollection */
453 {false, false}, /* CCRunnerState::CycleCollecting */
454 {false, false}}; /* CCRunnerState::Canceled */
456 ArrayLength(stateDescriptors
) == size_t(CCRunnerState::NumStates
),
457 "need one state descriptor per state");
458 const StateDescriptor
& desc
= stateDescriptors
[int(mCCRunnerState
)];
460 // Make sure we initialized the state machine.
461 MOZ_ASSERT(mCCRunnerState
!= CCRunnerState::Inactive
);
464 return {CCRunnerAction::StopRunning
, Yield
};
467 if (mCCRunnerState
== CCRunnerState::Canceled
) {
468 // When we cancel a cycle, there may have been a final ForgetSkippable.
469 return {CCRunnerAction::StopRunning
, Yield
};
472 TimeStamp now
= Now();
474 if (InIncrementalGC()) {
475 if (mCCBlockStart
.IsNull()) {
478 // If we have reached the CycleCollecting state, then ignore CC timer
479 // fires while incremental GC is running. (Running ICC during an IGC
480 // would cause us to synchronously finish the GC, which is bad.)
482 // If we have not yet started cycle collecting, then reset our state so
483 // that we run forgetSkippable often enough before CC. Because of reduced
484 // mCCDelay, forgetSkippable will be called just a few times.
486 // The kMaxCCLockedoutTime limit guarantees that we end up calling
487 // forgetSkippable and CycleCollectNow eventually.
489 if (mCCRunnerState
!= CCRunnerState::CycleCollecting
) {
490 mCCRunnerState
= CCRunnerState::ReducePurple
;
491 mCCRunnerEarlyFireCount
= 0;
492 mCCDelay
= kCCDelay
/ int64_t(3);
494 return {CCRunnerAction::None
, Yield
};
497 if (GetCCBlockedTime(now
) < kMaxCCLockedoutTime
) {
498 return {CCRunnerAction::None
, Yield
};
501 // Locked out for too long, so proceed and finish the incremental GC
505 // For states that aren't just continuations of previous states, check
506 // whether a CC is still needed (after doing various things to reduce the
508 if (desc
.mCanAbortCC
&& !IsCCNeeded(now
)) {
509 // If we don't pass the threshold for wanting to cycle collect, stop now
510 // (after possibly doing a final ForgetSkippable).
511 mCCRunnerState
= CCRunnerState::Canceled
;
512 NoteForgetSkippableOnlyCycle();
514 // Preserve the previous code's idea of when to check whether a
515 // ForgetSkippable should be fired.
516 if (desc
.mTryFinalForgetSkippable
&& ShouldForgetSkippable()) {
517 // The Canceled state will make us StopRunning after this action is
518 // performed (see conditional at top of function).
519 return {CCRunnerAction::ForgetSkippable
, Yield
, KeepChildless
};
522 return {CCRunnerAction::StopRunning
, Yield
};
525 switch (mCCRunnerState
) {
526 // ReducePurple: a GC ran (or we otherwise decided to try CC'ing). Wait
527 // for some amount of time (kCCDelay, or less if incremental GC blocked
528 // this CC) while firing regular ForgetSkippable actions before continuing
530 case CCRunnerState::ReducePurple
:
531 ++mCCRunnerEarlyFireCount
;
532 if (IsLastEarlyCCTimer(mCCRunnerEarlyFireCount
)) {
533 mCCRunnerState
= CCRunnerState::CleanupChildless
;
536 if (ShouldForgetSkippable()) {
537 return {CCRunnerAction::ForgetSkippable
, Yield
, KeepChildless
};
540 if (aDeadline
.IsNull()) {
541 return {CCRunnerAction::None
, Yield
};
544 // If we're called during idle time, try to find some work to do by
545 // advancing to the next state, effectively bypassing some possible forget
547 mCCRunnerState
= CCRunnerState::CleanupChildless
;
549 // Continue on to CleanupChildless, but only after checking IsCCNeeded
551 return {CCRunnerAction::None
, Continue
};
553 // CleanupChildless: do a stronger ForgetSkippable that removes nodes with
554 // no children in the cycle collector graph. This state is split into 3
555 // parts; the other Cleanup* actions will happen within the same callback
556 // (unless the ForgetSkippable shrinks the purple buffer enough for the CC
557 // to be skipped entirely.)
558 case CCRunnerState::CleanupChildless
:
559 mCCRunnerState
= CCRunnerState::CleanupContentUnbinder
;
560 return {CCRunnerAction::ForgetSkippable
, Yield
, RemoveChildless
};
562 // CleanupContentUnbinder: continuing cleanup, clear out the content
564 case CCRunnerState::CleanupContentUnbinder
:
565 if (aDeadline
.IsNull()) {
566 // Non-idle (waiting) callbacks skip the rest of the cleanup, but still
567 // wait for another fire before the actual CC.
568 mCCRunnerState
= CCRunnerState::StartCycleCollection
;
569 return {CCRunnerAction::None
, Yield
};
572 // Running in an idle callback.
574 // The deadline passed, so go straight to CC in the next slice.
575 if (now
>= aDeadline
) {
576 mCCRunnerState
= CCRunnerState::StartCycleCollection
;
577 return {CCRunnerAction::None
, Yield
};
580 mCCRunnerState
= CCRunnerState::CleanupDeferred
;
581 return {CCRunnerAction::CleanupContentUnbinder
, Continue
};
583 // CleanupDeferred: continuing cleanup, do deferred deletion.
584 case CCRunnerState::CleanupDeferred
:
585 MOZ_ASSERT(!aDeadline
.IsNull(),
586 "Should only be in CleanupDeferred state when idle");
588 // Our efforts to avoid a CC have failed. Let the timer fire once more
590 mCCRunnerState
= CCRunnerState::StartCycleCollection
;
591 if (now
>= aDeadline
) {
592 // The deadline passed, go straight to CC in the next slice.
593 return {CCRunnerAction::None
, Yield
};
596 return {CCRunnerAction::CleanupDeferred
, Yield
};
598 // StartCycleCollection: start actually doing cycle collection slices.
599 case CCRunnerState::StartCycleCollection
:
600 // We are in the final timer fire and still meet the conditions for
601 // triggering a CC. Let RunCycleCollectorSlice finish the current IGC if
602 // any, because that will allow us to include the GC time in the CC pause.
603 mCCRunnerState
= CCRunnerState::CycleCollecting
;
606 // CycleCollecting: continue running slices until done.
607 case CCRunnerState::CycleCollecting
:
608 return {CCRunnerAction::CycleCollect
, Yield
};
611 MOZ_CRASH("Unexpected CCRunner state");
615 inline js::SliceBudget
CCGCScheduler::ComputeForgetSkippableBudget(
616 TimeStamp aStartTimeStamp
, TimeStamp aDeadline
) {
617 if (mForgetSkippableFrequencyStartTime
.IsNull()) {
618 mForgetSkippableFrequencyStartTime
= aStartTimeStamp
;
619 } else if (aStartTimeStamp
- mForgetSkippableFrequencyStartTime
>
621 TimeStamp startPlusMinute
= mForgetSkippableFrequencyStartTime
+ kOneMinute
;
623 // If we had forget skippables only at the beginning of the interval, we
624 // still want to use the whole time, minute or more, for frequency
625 // calculation. mLastForgetSkippableEndTime is needed if forget skippable
626 // takes enough time to push the interval to be over a minute.
627 TimeStamp endPoint
= std::max(startPlusMinute
, mLastForgetSkippableEndTime
);
629 // Duration in minutes.
631 (endPoint
- mForgetSkippableFrequencyStartTime
).ToSeconds() / 60;
632 uint32_t frequencyPerMinute
= uint32_t(mForgetSkippableCounter
/ duration
);
633 Telemetry::Accumulate(Telemetry::FORGET_SKIPPABLE_FREQUENCY
,
635 mForgetSkippableCounter
= 0;
636 mForgetSkippableFrequencyStartTime
= aStartTimeStamp
;
638 ++mForgetSkippableCounter
;
640 TimeDuration budgetTime
=
641 aDeadline
? (aDeadline
- aStartTimeStamp
) : kForgetSkippableSliceDuration
;
642 return js::SliceBudget(budgetTime
);
645 } // namespace mozilla