Bug 1700051: part 35) Reduce accessibility of `mSoftText.mDOMMapping` to `private...
[gecko.git] / dom / base / CCGCScheduler.h
blobcd2c46eeff00096ccd8f180eab6e4ae7ce460eb9
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"
14 namespace mozilla {
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 {
62 None,
63 ForgetSkippable,
64 CleanupContentUnbinder,
65 CleanupDeferred,
66 CycleCollect,
67 StopRunning
70 enum CCRunnerYield { Continue, Yield };
72 enum CCRunnerForgetSkippableRemoveChildless {
73 KeepChildless = false,
74 RemoveChildless = true
77 struct CCRunnerStep {
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
82 // callback.
83 CCRunnerYield mYield;
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;
91 class CCGCScheduler {
92 public:
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();
104 // Parameter setting
106 void SetActiveIntersliceGCBudget(TimeDuration aDuration) {
107 mActiveIntersliceGCBudget = aDuration;
110 // State retrieval
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);
136 mNeedsFullCC = true;
137 mNeedsGCAfterCC = true;
140 void NoteGCBegin() {
141 // Treat all GC as incremental here; non-incremental GC will just appear to
142 // be one slice.
143 mInIncrementalGC = true;
146 void NoteGCEnd() {
147 mInIncrementalGC = false;
148 mCCBlockStart = TimeStamp();
149 mInIncrementalGC = false;
150 mNeedsFullCC = true;
151 mHasRunGC = true;
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
181 // decisions.
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; }
207 // Scheduling
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
211 // collection.
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 {
232 if (mNeedsFullCC) {
233 return true;
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 {
258 Inactive,
259 ReducePurple,
260 CleanupChildless,
261 CleanupContentUnbinder,
262 CleanupDeferred,
263 StartCycleCollection,
264 CycleCollecting,
265 Canceled,
266 NumStates
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) {
279 mCCDelay = kCCDelay;
280 mCCRunnerEarlyFireCount = 0;
281 } else if (initialState == CCRunnerState::CycleCollecting) {
282 // Nothing needed.
283 } else {
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);
297 private:
298 // State
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
321 // gray bits.
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;
331 public:
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
381 // laterSliceBudget.
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) {
395 return budget;
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 {
406 if (!mHasRunGC) {
407 return false;
410 TimeStamp now = Now();
412 // Don't run consecutive CCs too often.
413 if (mCleanupsSinceLastGC && !mLastCCEndTime.IsNull()) {
414 if (now - mLastCCEndTime < kCCDelay) {
415 return false;
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) {
425 return false;
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?
436 bool mCanAbortCC;
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 */
455 static_assert(
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);
463 if (mDidShutdown) {
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()) {
476 BlockCC(now);
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
502 // synchronously.
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
507 // purple buffer).
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
529 // on.
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
546 // skippable calls.
547 mCCRunnerState = CCRunnerState::CleanupChildless;
549 // Continue on to CleanupChildless, but only after checking IsCCNeeded
550 // again.
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
563 // unbinder.
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
589 // to trigger a CC.
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;
604 [[fallthrough]];
606 // CycleCollecting: continue running slices until done.
607 case CCRunnerState::CycleCollecting:
608 return {CCRunnerAction::CycleCollect, Yield};
610 default:
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 >
620 kOneMinute) {
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.
630 double duration =
631 (endPoint - mForgetSkippableFrequencyStartTime).ToSeconds() / 60;
632 uint32_t frequencyPerMinute = uint32_t(mForgetSkippableCounter / duration);
633 Telemetry::Accumulate(Telemetry::FORGET_SKIPPABLE_FREQUENCY,
634 frequencyPerMinute);
635 mForgetSkippableCounter = 0;
636 mForgetSkippableFrequencyStartTime = aStartTimeStamp;
638 ++mForgetSkippableCounter;
640 TimeDuration budgetTime =
641 aDeadline ? (aDeadline - aStartTimeStamp) : kForgetSkippableSliceDuration;
642 return js::SliceBudget(budgetTime);
645 } // namespace mozilla