]> git.saurik.com Git - apple/xnu.git/blobdiff - bsd/kern/kern_memorystatus_freeze.c
xnu-6153.101.6.tar.gz
[apple/xnu.git] / bsd / kern / kern_memorystatus_freeze.c
index c83a80d72bca65182938456de4603971d5b21dfe..a6e720285279098521085bc03c733b2e2b51d207 100644 (file)
@@ -128,6 +128,53 @@ unsigned int memorystatus_freeze_private_shared_pages_ratio = 2; /* Ratio of pri
 unsigned int memorystatus_thaw_count = 0;
 unsigned int memorystatus_refreeze_eligible_count = 0; /* # of processes currently thawed i.e. have state on disk & in-memory */
 
+/* Freezer counters collected for telemtry */
+static struct memorystatus_freezer_stats_t {
+       /*
+        * # of processes that we've considered freezing.
+        * Used to normalize the error reasons below.
+        */
+       uint64_t mfs_process_considered_count;
+
+       /*
+        * The following counters track how many times we've failed to freeze
+        * a process because of a specific FREEZER_ERROR.
+        */
+       /* EXCESS_SHARED_MEMORY */
+       uint64_t mfs_error_excess_shared_memory_count;
+       /* LOW_PRIVATE_SHARED_RATIO */
+       uint64_t mfs_error_low_private_shared_ratio_count;
+       /* NO_COMPRESSOR_SPACE */
+       uint64_t mfs_error_no_compressor_space_count;
+       /* NO_SWAP_SPACE */
+       uint64_t mfs_error_no_swap_space_count;
+       /* pages < memorystatus_freeze_pages_min */
+       uint64_t mfs_error_below_min_pages_count;
+       /* dasd determined it was unlikely to be relaunched. */
+       uint64_t mfs_error_low_probability_of_use_count;
+       /* transient reasons (like inability to acquire a lock). */
+       uint64_t mfs_error_other_count;
+
+       /*
+        * # of times that we saw memorystatus_available_pages <= memorystatus_freeze_threshold.
+        * Used to normalize skipped_full_count and shared_mb_high_count.
+        */
+       uint64_t mfs_below_threshold_count;
+
+       /* Skipped running the freezer because we were out of slots */
+       uint64_t mfs_skipped_full_count;
+
+       /* Skipped running the freezer because we were over the shared mb limit*/
+       uint64_t mfs_skipped_shared_mb_high_count;
+
+       /*
+        * How many pages have not been sent to swap because they were in a shared object?
+        * This is being used to gather telemtry so we can understand the impact we'd have
+        * on our NAND budget if we did swap out these pages.
+        */
+       uint64_t mfs_shared_pages_skipped;
+} memorystatus_freezer_stats = {0};
+
 #endif /* XNU_KERNEL_PRIVATE */
 
 static inline boolean_t memorystatus_can_freeze_processes(void);
@@ -144,6 +191,7 @@ static uint64_t memorystatus_freeze_pageouts = 0;
 #define DEGRADED_WINDOW_MINS    (30)
 #define NORMAL_WINDOW_MINS      (24 * 60)
 
+/* Protected by the freezer_mutex */
 static throttle_interval_t throttle_intervals[] = {
        { DEGRADED_WINDOW_MINS, 1, 0, 0, { 0, 0 }},
        { NORMAL_WINDOW_MINS, 1, 0, 0, { 0, 0 }},
@@ -166,6 +214,52 @@ SYSCTL_UINT(_kern, OID_AUTO, memorystatus_freeze_count, CTLFLAG_RD | CTLFLAG_LOC
 SYSCTL_UINT(_kern, OID_AUTO, memorystatus_thaw_count, CTLFLAG_RD | CTLFLAG_LOCKED, &memorystatus_thaw_count, 0, "");
 SYSCTL_QUAD(_kern, OID_AUTO, memorystatus_freeze_pageouts, CTLFLAG_RD | CTLFLAG_LOCKED, &memorystatus_freeze_pageouts, "");
 SYSCTL_QUAD(_kern, OID_AUTO, memorystatus_freeze_budget_pages_remaining, CTLFLAG_RD | CTLFLAG_LOCKED, &memorystatus_freeze_budget_pages_remaining, "");
+SYSCTL_QUAD(_kern, OID_AUTO, memorystatus_freezer_error_excess_shared_memory_count, CTLFLAG_RD | CTLFLAG_LOCKED, &memorystatus_freezer_stats.mfs_error_excess_shared_memory_count, "");
+SYSCTL_QUAD(_kern, OID_AUTO, memorystatus_freezer_error_low_private_shared_ratio_count, CTLFLAG_RD | CTLFLAG_LOCKED, &memorystatus_freezer_stats.mfs_error_low_private_shared_ratio_count, "");
+SYSCTL_QUAD(_kern, OID_AUTO, memorystatus_freezer_error_no_compressor_space_count, CTLFLAG_RD | CTLFLAG_LOCKED, &memorystatus_freezer_stats.mfs_error_no_compressor_space_count, "");
+SYSCTL_QUAD(_kern, OID_AUTO, memorystatus_freezer_error_no_swap_space_count, CTLFLAG_RD | CTLFLAG_LOCKED, &memorystatus_freezer_stats.mfs_error_no_swap_space_count, "");
+SYSCTL_QUAD(_kern, OID_AUTO, memorystatus_freezer_error_below_min_pages_count, CTLFLAG_RD | CTLFLAG_LOCKED, &memorystatus_freezer_stats.mfs_error_below_min_pages_count, "");
+SYSCTL_QUAD(_kern, OID_AUTO, memorystatus_freezer_error_low_probability_of_use_count, CTLFLAG_RD | CTLFLAG_LOCKED, &memorystatus_freezer_stats.mfs_error_low_probability_of_use_count, "");
+SYSCTL_QUAD(_kern, OID_AUTO, memorystatus_freezer_error_other_count, CTLFLAG_RD | CTLFLAG_LOCKED, &memorystatus_freezer_stats.mfs_error_other_count, "");
+SYSCTL_QUAD(_kern, OID_AUTO, memorystatus_freezer_process_considered_count, CTLFLAG_RD | CTLFLAG_LOCKED, &memorystatus_freezer_stats.mfs_process_considered_count, "");
+SYSCTL_QUAD(_kern, OID_AUTO, memorystatus_freezer_below_threshold_count, CTLFLAG_RD | CTLFLAG_LOCKED, &memorystatus_freezer_stats.mfs_below_threshold_count, "");
+SYSCTL_QUAD(_kern, OID_AUTO, memorystatus_freezer_skipped_full_count, CTLFLAG_RD | CTLFLAG_LOCKED, &memorystatus_freezer_stats.mfs_skipped_full_count, "");
+SYSCTL_QUAD(_kern, OID_AUTO, memorystatus_freezer_skipped_shared_mb_high_count, CTLFLAG_RD | CTLFLAG_LOCKED, &memorystatus_freezer_stats.mfs_skipped_shared_mb_high_count, "");
+SYSCTL_QUAD(_kern, OID_AUTO, memorystatus_freezer_shared_pages_skipped, CTLFLAG_RD | CTLFLAG_LOCKED, &memorystatus_freezer_stats.mfs_shared_pages_skipped, "");
+
+/*
+ * Calculates the hit rate for the freezer.
+ * The hit rate is defined as the percentage of procs that are currently in the
+ * freezer which we have thawed.
+ * A low hit rate means we're freezing bad candidates since they're not re-used.
+ */
+static int sysctl_memorystatus_freezer_thaw_percentage SYSCTL_HANDLER_ARGS
+{
+#pragma unused(arg1, arg2)
+       size_t thaw_count = 0, frozen_count = 0;
+       int thaw_percentage = 100;
+       unsigned int band = (unsigned int) memorystatus_freeze_jetsam_band;
+       proc_t p = PROC_NULL;
+       proc_list_lock();
+
+       p = memorystatus_get_first_proc_locked(&band, FALSE);
+
+       while (p) {
+               if (p->p_memstat_state & P_MEMSTAT_FROZEN) {
+                       if (p->p_memstat_thaw_count > 0) {
+                               thaw_count++;
+                       }
+                       frozen_count++;
+               }
+               p = memorystatus_get_next_proc_locked(&band, p, FALSE);
+       }
+       proc_list_unlock();
+       if (frozen_count > 0) {
+               thaw_percentage = 100 * thaw_count / frozen_count;
+       }
+       return sysctl_handle_int(oidp, &thaw_percentage, 0, req);
+}
+SYSCTL_PROC(_kern, OID_AUTO, memorystatus_freezer_thaw_percentage, CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_LOCKED, 0, 0, &sysctl_memorystatus_freezer_thaw_percentage, "I", "");
 
 
 #if DEVELOPMENT || DEBUG
@@ -248,6 +342,7 @@ sysctl_memorystatus_freeze SYSCTL_HANDLER_ARGS
 again:
        p = proc_find(pid);
        if (p != NULL) {
+               memorystatus_freezer_stats.mfs_process_considered_count++;
                uint32_t purgeable, wired, clean, dirty, shared;
                uint32_t max_pages = 0, state = 0;
 
@@ -297,18 +392,24 @@ again:
                }
 
                error = task_freeze(p->task, &purgeable, &wired, &clean, &dirty, max_pages, &shared, &freezer_error_code, FALSE /* eval only */);
+               if (!error || freezer_error_code == FREEZER_ERROR_LOW_PRIVATE_SHARED_RATIO) {
+                       memorystatus_freezer_stats.mfs_shared_pages_skipped += shared;
+               }
 
                if (error) {
                        char reason[128];
                        if (freezer_error_code == FREEZER_ERROR_EXCESS_SHARED_MEMORY) {
+                               memorystatus_freezer_stats.mfs_error_excess_shared_memory_count++;
                                strlcpy(reason, "too much shared memory", 128);
                        }
 
                        if (freezer_error_code == FREEZER_ERROR_LOW_PRIVATE_SHARED_RATIO) {
+                               memorystatus_freezer_stats.mfs_error_low_private_shared_ratio_count++;
                                strlcpy(reason, "low private-shared pages ratio", 128);
                        }
 
                        if (freezer_error_code == FREEZER_ERROR_NO_COMPRESSOR_SPACE) {
+                               memorystatus_freezer_stats.mfs_error_no_compressor_space_count++;
                                strlcpy(reason, "no compressor space", 128);
                        }
 
@@ -402,11 +503,20 @@ static int
 sysctl_memorystatus_demote_frozen_process SYSCTL_HANDLER_ARGS
 {
 #pragma unused(arg1, arg2, oidp, req)
+       int error, val;
+       /*
+        * Only demote on write to prevent demoting during `sysctl -a`.
+        * The actual value written doesn't matter.
+        */
+       error = sysctl_handle_int(oidp, &val, 0, req);
+       if (error || !req->newptr) {
+               return error;
+       }
        memorystatus_demote_frozen_processes(false);
        return 0;
 }
 
-SYSCTL_PROC(_kern, OID_AUTO, memorystatus_demote_frozen_processes, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_LOCKED | CTLFLAG_MASKED, 0, 0, &sysctl_memorystatus_demote_frozen_process, "I", "");
+SYSCTL_PROC(_kern, OID_AUTO, memorystatus_demote_frozen_processes, CTLTYPE_INT | CTLFLAG_WR | CTLFLAG_LOCKED | CTLFLAG_MASKED, 0, 0, &sysctl_memorystatus_demote_frozen_process, "I", "");
 
 static int
 sysctl_memorystatus_available_pages_thaw SYSCTL_HANDLER_ARGS
@@ -1081,10 +1191,17 @@ memorystatus_is_process_eligible_for_freeze(proc_t p)
                }
        }
 
+       /*
+        * This proc is a suspended application.
+        * We're interested in tracking what percentage of these
+        * actually get frozen.
+        */
+       memorystatus_freezer_stats.mfs_process_considered_count++;
 
        /* Only freeze applications meeting our minimum resident page criteria */
        memorystatus_get_task_page_counts(p->task, &pages, NULL, NULL);
        if (pages < memorystatus_freeze_pages_min) {
+               memorystatus_freezer_stats.mfs_error_below_min_pages_count++;
                goto out;
        }
 
@@ -1094,6 +1211,7 @@ memorystatus_is_process_eligible_for_freeze(proc_t p)
         * memorystatus_freeze_top_process holds the proc_list_lock while it traverses the bands.
         */
        if ((p->p_listflag & P_LIST_EXITED) != 0) {
+               memorystatus_freezer_stats.mfs_error_other_count++;
                goto out;
        }
 
@@ -1110,6 +1228,7 @@ memorystatus_is_process_eligible_for_freeze(proc_t p)
                }
 
                if (probability_of_use == 0) {
+                       memorystatus_freezer_stats.mfs_error_low_probability_of_use_count++;
                        goto out;
                }
        }
@@ -1196,6 +1315,9 @@ memorystatus_freeze_process_sync(proc_t p)
                    memorystatus_available_pages, 0, 0, 0, 0);
 
                ret = task_freeze(p->task, &purgeable, &wired, &clean, &dirty, max_pages, &shared, &freezer_error_code, FALSE /* eval only */);
+               if (ret == KERN_SUCCESS || freezer_error_code == FREEZER_ERROR_LOW_PRIVATE_SHARED_RATIO) {
+                       memorystatus_freezer_stats.mfs_shared_pages_skipped += shared;
+               }
 
                KERNEL_DEBUG_CONSTANT(BSDDBG_CODE(DBG_BSD_MEMSTAT, BSD_MEMSTAT_FREEZE) | DBG_FUNC_END,
                    memorystatus_available_pages, aPid, 0, 0, 0);
@@ -1241,15 +1363,17 @@ memorystatus_freeze_process_sync(proc_t p)
                                        ret = 0;
                                }
 
-                               proc_list_lock();
 
                                /* Update stats */
                                for (i = 0; i < sizeof(throttle_intervals) / sizeof(struct throttle_interval_t); i++) {
                                        throttle_intervals[i].pageouts += dirty;
                                }
-                       } else {
-                               proc_list_lock();
                        }
+                       memorystatus_freeze_update_throttle(&memorystatus_freeze_budget_pages_remaining);
+                       os_log_with_startup_serial(OS_LOG_DEFAULT, "memorystatus: freezing (specific) pid %d [%s] done memorystatus_freeze_budget_pages_remaining %llu froze %u pages",
+                           aPid, ((p && *p->p_name) ? p->p_name : "unknown"), memorystatus_freeze_budget_pages_remaining, dirty);
+
+                       proc_list_lock();
 
                        memorystatus_freeze_pageouts += dirty;
 
@@ -1260,25 +1384,25 @@ memorystatus_freeze_process_sync(proc_t p)
                                 * can freeze a more eligible process at this moment in time?
                                 */
                        }
-
-                       memorystatus_freeze_update_throttle(&memorystatus_freeze_budget_pages_remaining);
-                       os_log_with_startup_serial(OS_LOG_DEFAULT, "memorystatus: freezing (specific) pid %d [%s] done memorystatus_freeze_budget_pages_remaining %llu froze %u pages",
-                           aPid, ((p && *p->p_name) ? p->p_name : "unknown"), memorystatus_freeze_budget_pages_remaining, dirty);
                } else {
                        char reason[128];
                        if (freezer_error_code == FREEZER_ERROR_EXCESS_SHARED_MEMORY) {
+                               memorystatus_freezer_stats.mfs_error_excess_shared_memory_count++;
                                strlcpy(reason, "too much shared memory", 128);
                        }
 
                        if (freezer_error_code == FREEZER_ERROR_LOW_PRIVATE_SHARED_RATIO) {
+                               memorystatus_freezer_stats.mfs_error_low_private_shared_ratio_count++;
                                strlcpy(reason, "low private-shared pages ratio", 128);
                        }
 
                        if (freezer_error_code == FREEZER_ERROR_NO_COMPRESSOR_SPACE) {
+                               memorystatus_freezer_stats.mfs_error_no_compressor_space_count++;
                                strlcpy(reason, "no compressor space", 128);
                        }
 
                        if (freezer_error_code == FREEZER_ERROR_NO_SWAP_SPACE) {
+                               memorystatus_freezer_stats.mfs_error_no_swap_space_count++;
                                strlcpy(reason, "no swap space", 128);
                        }
 
@@ -1298,6 +1422,9 @@ exit:
        return ret;
 }
 
+/*
+ * Caller must hold the freezer_mutex and it will be locked on return.
+ */
 static int
 memorystatus_freeze_top_process(void)
 {
@@ -1311,6 +1438,7 @@ memorystatus_freeze_top_process(void)
        coalition_t coal = COALITION_NULL;
        pid_t pid_list[MAX_XPC_SERVICE_PIDS];
        unsigned int    ntasks = 0;
+       LCK_MTX_ASSERT(&freezer_mutex, LCK_MTX_ASSERT_OWNED);
 
        KERNEL_DEBUG_CONSTANT(BSDDBG_CODE(DBG_BSD_MEMSTAT, BSD_MEMSTAT_FREEZE_SCAN) | DBG_FUNC_START, memorystatus_available_pages, 0, 0, 0, 0);
 
@@ -1432,6 +1560,7 @@ freeze_process:
 
                p = proc_ref_locked(p);
                if (!p) {
+                       memorystatus_freezer_stats.mfs_error_other_count++;
                        break;
                }
 
@@ -1441,6 +1570,9 @@ freeze_process:
                    memorystatus_available_pages, 0, 0, 0, 0);
 
                kr = task_freeze(p->task, &purgeable, &wired, &clean, &dirty, max_pages, &shared, &freezer_error_code, FALSE /* eval only */);
+               if (kr == KERN_SUCCESS || freezer_error_code == FREEZER_ERROR_LOW_PRIVATE_SHARED_RATIO) {
+                       memorystatus_freezer_stats.mfs_shared_pages_skipped += shared;
+               }
 
                KERNEL_DEBUG_CONSTANT(BSDDBG_CODE(DBG_BSD_MEMSTAT, BSD_MEMSTAT_FREEZE) | DBG_FUNC_END,
                    memorystatus_available_pages, aPid, 0, 0, 0);
@@ -1484,15 +1616,16 @@ freeze_process:
                                        ret = 0;
                                }
 
-                               proc_list_lock();
-
                                /* Update stats */
                                for (i = 0; i < sizeof(throttle_intervals) / sizeof(struct throttle_interval_t); i++) {
                                        throttle_intervals[i].pageouts += dirty;
                                }
-                       } else {
-                               proc_list_lock();
                        }
+                       memorystatus_freeze_update_throttle(&memorystatus_freeze_budget_pages_remaining);
+                       os_log_with_startup_serial(OS_LOG_DEFAULT, "memorystatus: %sfreezing (%s) pid %d [%s] done, memorystatus_freeze_budget_pages_remaining %llu %sfroze %u pages\n",
+                           refreeze_processes? "re" : "", (coal == NULL ? "general" : "coalition-driven"), aPid, ((p && *p->p_name) ? p->p_name : "unknown"), memorystatus_freeze_budget_pages_remaining, refreeze_processes? "Re" : "", dirty);
+
+                       proc_list_lock();
 
                        memorystatus_freeze_pageouts += dirty;
 
@@ -1504,10 +1637,6 @@ freeze_process:
                                 */
                        }
 
-                       memorystatus_freeze_update_throttle(&memorystatus_freeze_budget_pages_remaining);
-                       os_log_with_startup_serial(OS_LOG_DEFAULT, "memorystatus: %sfreezing (%s) pid %d [%s] done, memorystatus_freeze_budget_pages_remaining %llu %sfroze %u pages\n",
-                           refreeze_processes? "re" : "", (coal == NULL ? "general" : "coalition-driven"), aPid, ((p && *p->p_name) ? p->p_name : "unknown"), memorystatus_freeze_budget_pages_remaining, refreeze_processes? "Re" : "", dirty);
-
                        /* Return KERN_SUCCESS */
                        ret = kr;
 
@@ -1603,18 +1732,22 @@ freeze_process:
 
                        char reason[128];
                        if (freezer_error_code == FREEZER_ERROR_EXCESS_SHARED_MEMORY) {
+                               memorystatus_freezer_stats.mfs_error_excess_shared_memory_count++;
                                strlcpy(reason, "too much shared memory", 128);
                        }
 
                        if (freezer_error_code == FREEZER_ERROR_LOW_PRIVATE_SHARED_RATIO) {
+                               memorystatus_freezer_stats.mfs_error_low_private_shared_ratio_count++;
                                strlcpy(reason, "low private-shared pages ratio", 128);
                        }
 
                        if (freezer_error_code == FREEZER_ERROR_NO_COMPRESSOR_SPACE) {
+                               memorystatus_freezer_stats.mfs_error_no_compressor_space_count++;
                                strlcpy(reason, "no compressor space", 128);
                        }
 
                        if (freezer_error_code == FREEZER_ERROR_NO_SWAP_SPACE) {
+                               memorystatus_freezer_stats.mfs_error_no_swap_space_count++;
                                strlcpy(reason, "no swap space", 128);
                        }
 
@@ -1836,8 +1969,8 @@ memorystatus_demote_frozen_processes(boolean_t force_one)
 
        if (force_one == FALSE) {
                /*
-                * We use this counter to track daily thaws.
-                * So we only reset it to 0 under the normal
+                * We use these counters to track daily hit rates.
+                * So we only reset them to 0 under the normal
                 * mode.
                 */
                memorystatus_thaw_count = 0;
@@ -1846,6 +1979,72 @@ memorystatus_demote_frozen_processes(boolean_t force_one)
        proc_list_unlock();
 }
 
+/*
+ * Calculate a new freezer budget.
+ * @param time_since_last_interval_expired_sec How long has it been (in seconds) since the previous interval expired.
+ * @param burst_multiple The burst_multiple for the new period
+ * @param interval_duration_min How many minutes will the new interval be?
+ * @param rollover The amount to rollover from the previous budget.
+ *
+ * @return A budget for the new interval.
+ */
+static uint32_t
+memorystatus_freeze_calculate_new_budget(
+       unsigned int time_since_last_interval_expired_sec,
+       unsigned int burst_multiple,
+       unsigned int interval_duration_min,
+       uint32_t rollover)
+{
+       uint64_t freeze_daily_budget = 0;
+       unsigned int daily_budget_pageouts = 0;
+       unsigned int freeze_daily_pageouts_max = 0;
+       const static unsigned int kNumSecondsInDay = 60 * 60 * 24;
+       /* Precision factor for days_missed. 2 decimal points. */
+       const static unsigned int kFixedPointFactor = 100;
+       unsigned int days_missed, budget_missed;
+
+       /* Get the daily budget from the storage layer */
+       if (vm_swap_max_budget(&freeze_daily_budget)) {
+               memorystatus_freeze_daily_mb_max = (freeze_daily_budget / (1024 * 1024));
+               os_log_with_startup_serial(OS_LOG_DEFAULT, "memorystatus: memorystatus_freeze_daily_mb_max set to %dMB\n", memorystatus_freeze_daily_mb_max);
+       }
+       /* Calculate the daily pageout budget */
+       freeze_daily_pageouts_max = memorystatus_freeze_daily_mb_max * (1024 * 1024 / PAGE_SIZE);
+
+       daily_budget_pageouts = (burst_multiple * (((uint64_t) interval_duration_min * freeze_daily_pageouts_max) / (kNumSecondsInDay / 60)));
+
+       /*
+        * Add additional budget for time since the interval expired.
+        * For example, if the interval expired n days ago, we should get an additional n days
+        * of budget since we didn't use any budget during those n days.
+        */
+       days_missed = time_since_last_interval_expired_sec * kFixedPointFactor / kNumSecondsInDay;
+       budget_missed = days_missed * freeze_daily_pageouts_max / kFixedPointFactor;
+       return rollover + daily_budget_pageouts + budget_missed;
+}
+
+#if DEVELOPMENT || DEBUG
+
+static int
+sysctl_memorystatus_freeze_calculate_new_budget SYSCTL_HANDLER_ARGS
+{
+#pragma unused(arg1, arg2)
+       int error = 0;
+       unsigned int time_since_last_interval_expired_sec = 0;
+       unsigned int new_budget;
+
+       error = sysctl_handle_int(oidp, &time_since_last_interval_expired_sec, 0, req);
+       if (error || !req->newptr) {
+               return error;
+       }
+       new_budget = memorystatus_freeze_calculate_new_budget(time_since_last_interval_expired_sec, 1, NORMAL_WINDOW_MINS, 0);
+       return copyout(&new_budget, req->oldptr, MIN(sizeof(req->oldlen), sizeof(new_budget)));
+}
+
+SYSCTL_PROC(_vm, OID_AUTO, memorystatus_freeze_calculate_new_budget, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MASKED,
+    0, 0, &sysctl_memorystatus_freeze_calculate_new_budget, "I", "");
+
+#endif /* DEVELOPMENT || DEBUG */
 
 /*
  * This function will do 4 things:
@@ -1861,6 +2060,9 @@ memorystatus_demote_frozen_processes(boolean_t force_one)
  * 4) calculate the current rate of pageouts for DEGRADED_WINDOW_MINS duration. If that rate is below
  *    what we would normally expect, then we are running low on our daily budget and need to enter
  *    degraded perf. mode.
+ *
+ *    Caller must hold the freezer mutex
+ *    Caller must not hold the proc_list lock
  */
 
 static void
@@ -1868,7 +2070,9 @@ memorystatus_freeze_update_throttle(uint64_t *budget_pages_allowed)
 {
        clock_sec_t sec;
        clock_nsec_t nsec;
-       mach_timespec_t ts;
+       mach_timespec_t now_ts;
+       LCK_MTX_ASSERT(&freezer_mutex, LCK_MTX_ASSERT_OWNED);
+       LCK_MTX_ASSERT(proc_list_mlock, LCK_MTX_ASSERT_NOTOWNED);
 
        unsigned int freeze_daily_pageouts_max = 0;
 
@@ -1883,15 +2087,15 @@ memorystatus_freeze_update_throttle(uint64_t *budget_pages_allowed)
 #endif
 
        clock_get_system_nanotime(&sec, &nsec);
-       ts.tv_sec = sec;
-       ts.tv_nsec = nsec;
+       now_ts.tv_sec = sec;
+       now_ts.tv_nsec = nsec;
 
        struct throttle_interval_t *interval = NULL;
 
        if (memorystatus_freeze_degradation == TRUE) {
                interval = degraded_throttle_window;
 
-               if (CMP_MACH_TIMESPEC(&ts, &interval->ts) >= 0) {
+               if (CMP_MACH_TIMESPEC(&now_ts, &interval->ts) >= 0) {
                        memorystatus_freeze_degradation = FALSE;
                        interval->pageouts = 0;
                        interval->max_pageouts = 0;
@@ -1902,28 +2106,17 @@ memorystatus_freeze_update_throttle(uint64_t *budget_pages_allowed)
 
        interval = normal_throttle_window;
 
-       if (CMP_MACH_TIMESPEC(&ts, &interval->ts) >= 0) {
-               /*
-                * New throttle window.
-                * Rollover any unused budget.
-                * Also ask the storage layer what the new budget needs to be.
-                */
-               uint64_t freeze_daily_budget = 0;
-               unsigned int daily_budget_pageouts = 0;
-
-               if (vm_swap_max_budget(&freeze_daily_budget)) {
-                       memorystatus_freeze_daily_mb_max = (freeze_daily_budget / (1024 * 1024));
-                       os_log_with_startup_serial(OS_LOG_DEFAULT, "memorystatus: memorystatus_freeze_daily_mb_max set to %dMB\n", memorystatus_freeze_daily_mb_max);
-               }
-
-               freeze_daily_pageouts_max = memorystatus_freeze_daily_mb_max * (1024 * 1024 / PAGE_SIZE);
-
-               daily_budget_pageouts =  (interval->burst_multiple * (((uint64_t)interval->mins * freeze_daily_pageouts_max) / NORMAL_WINDOW_MINS));
-               interval->max_pageouts = (interval->max_pageouts - interval->pageouts) + daily_budget_pageouts;
+       if (CMP_MACH_TIMESPEC(&now_ts, &interval->ts) >= 0) {
+               /* How long has it been since the previous interval expired? */
+               mach_timespec_t expiration_period_ts = now_ts;
+               SUB_MACH_TIMESPEC(&expiration_period_ts, &interval->ts);
 
+               interval->max_pageouts = memorystatus_freeze_calculate_new_budget(
+                       expiration_period_ts.tv_sec, interval->burst_multiple,
+                       interval->mins, interval->max_pageouts - interval->pageouts);
                interval->ts.tv_sec = interval->mins * 60;
                interval->ts.tv_nsec = 0;
-               ADD_MACH_TIMESPEC(&interval->ts, &ts);
+               ADD_MACH_TIMESPEC(&interval->ts, &now_ts);
                /* Since we update the throttle stats pre-freeze, adjust for overshoot here */
                if (interval->pageouts > interval->max_pageouts) {
                        interval->pageouts -= interval->max_pageouts;
@@ -1931,6 +2124,7 @@ memorystatus_freeze_update_throttle(uint64_t *budget_pages_allowed)
                        interval->pageouts = 0;
                }
                *budget_pages_allowed = interval->max_pageouts;
+               memorystatus_freezer_stats.mfs_shared_pages_skipped = 0;
 
                memorystatus_demote_frozen_processes(FALSE); /* normal mode...don't force a demotion */
        } else {
@@ -1968,7 +2162,7 @@ memorystatus_freeze_update_throttle(uint64_t *budget_pages_allowed)
                                time_left.tv_sec = interval->ts.tv_sec;
                                time_left.tv_nsec = 0;
 
-                               SUB_MACH_TIMESPEC(&time_left, &ts);
+                               SUB_MACH_TIMESPEC(&time_left, &now_ts);
 
                                if (budget_left <= budget_threshold) {
                                        /*
@@ -2004,7 +2198,7 @@ memorystatus_freeze_update_throttle(uint64_t *budget_pages_allowed)
        }
 
        MEMORYSTATUS_DEBUG(1, "memorystatus_freeze_update_throttle_interval: throttle updated - %d frozen (%d max) within %dm; %dm remaining; throttle %s\n",
-           interval->pageouts, interval->max_pageouts, interval->mins, (interval->ts.tv_sec - ts->tv_sec) / 60,
+           interval->pageouts, interval->max_pageouts, interval->mins, (interval->ts.tv_sec - now_ts->tv_sec) / 60,
            interval->throttle ? "on" : "off");
 }
 
@@ -2063,12 +2257,21 @@ memorystatus_freeze_thread_should_run(void)
                goto out;
        }
 
-       if ((memorystatus_frozen_count >= memorystatus_frozen_processes_max) &&
-           (memorystatus_refreeze_eligible_count < MIN_THAW_REFREEZE_THRESHOLD)) {
-               goto out;
+       memorystatus_freezer_stats.mfs_below_threshold_count++;
+
+       if ((memorystatus_frozen_count >= memorystatus_frozen_processes_max)) {
+               /*
+                * Consider this as a skip even if we wake up to refreeze because
+                * we won't freeze any new procs.
+                */
+               memorystatus_freezer_stats.mfs_skipped_full_count++;
+               if (memorystatus_refreeze_eligible_count < MIN_THAW_REFREEZE_THRESHOLD) {
+                       goto out;
+               }
        }
 
        if (memorystatus_frozen_shared_mb_max && (memorystatus_frozen_shared_mb >= memorystatus_frozen_shared_mb_max)) {
+               memorystatus_freezer_stats.mfs_skipped_shared_mb_high_count++;
                goto out;
        }