| // Copyright 2022 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "vm_tools/concierge/balloon_policy.h" |
| #include "vm_tools/concierge/vm_util.h" |
| |
| #include <string> |
| |
| #include <base/files/file_path.h> |
| #include <base/files/file_util.h> |
| #include <base/json/json_reader.h> |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| #include <metrics/metrics_library_mock.h> |
| |
| #include "vm_tools/concierge/byte_unit.h" |
| #include "vm_tools/concierge/mm/balloon_metrics.h" |
| |
| namespace vm_tools { |
| namespace concierge { |
| |
| namespace { |
| constexpr int64_t PAGE_BYTES = 4096; |
| } // namespace |
| |
| // Test that shared and unevictable memory is subtracted from disk caches when |
| // checking if the guest has low caches. |
| TEST(BalloonPolocyTest, Unreclaimable) { |
| // Values are roughly what a 4GB ARCVM would get (but rounded) |
| const int64_t host_lwm = MiB(200); |
| ZoneInfoStats guest_stats = {.sum_low = MiB(200), .totalreserve = MiB(300)}; |
| MemoryMargins margins = {.critical = MiB(400), .moderate = MiB(2000)}; |
| const LimitCacheBalloonPolicy::Params params = { |
| .reclaim_target_cache = 0, |
| .critical_target_cache = 0, |
| .moderate_target_cache = MiB(200)}; |
| const auto metrics_library = std::make_unique<MetricsLibraryMock>(); |
| const auto metrics = std::make_unique<mm::BalloonMetrics>( |
| apps::VmType::ARCVM, |
| raw_ref<MetricsLibraryInterface>::from_ptr(metrics_library.get())); |
| LimitCacheBalloonPolicy policy( |
| margins, host_lwm, guest_stats, params, "test", |
| raw_ref<mm::BalloonMetrics>::from_ptr(metrics.get())); |
| |
| // Test that when, because of unevictable memory, there is less cache left |
| // than the cache limit, that we keep free_memory at MaxFree. |
| { |
| BalloonStats stats = {{.free_memory = policy.MaxFree(), |
| .disk_caches = MiB(300), |
| .unevictable_memory = MiB(101)}}; |
| EXPECT_EQ( |
| 0, policy.ComputeBalloonDeltaImpl(0 /* host_free */, stats, |
| margins.moderate /* host_available */, |
| false, "test", 0, {})); |
| } |
| |
| // Test that when, because of shared memory, there is less cache left than the |
| // cache limit, that we keep free_memory at MaxFree. |
| { |
| BalloonStats stats = {{.free_memory = policy.MaxFree(), |
| .disk_caches = MiB(300), |
| |
| .shared_memory = MiB(101)}}; |
| EXPECT_EQ( |
| 0, policy.ComputeBalloonDeltaImpl(0 /* host_free */, stats, |
| margins.moderate /* host_available */, |
| false, "test", 0, {})); |
| } |
| } |
| |
| // Test that having no limits still inflates the balloon to reduce excess free. |
| TEST(BalloonPolicyTest, LimitCacheNoLimit) { |
| const int64_t host_lwm = MiB(200); |
| ZoneInfoStats guest_stats = {.sum_low = MiB(200), .totalreserve = MiB(300)}; |
| const MemoryMargins margins = {.critical = MiB(400), .moderate = MiB(2000)}; |
| const LimitCacheBalloonPolicy::Params params = {.reclaim_target_cache = 0, |
| .critical_target_cache = 0, |
| .moderate_target_cache = 0}; |
| const auto metrics_library = std::make_unique<MetricsLibraryMock>(); |
| const auto metrics = std::make_unique<mm::BalloonMetrics>( |
| apps::VmType::ARCVM, |
| raw_ref<MetricsLibraryInterface>::from_ptr(metrics_library.get())); |
| LimitCacheBalloonPolicy policy( |
| margins, host_lwm, guest_stats, params, "test", |
| raw_ref<mm::BalloonMetrics>::from_ptr(metrics.get())); |
| |
| // NB: Because there are no cache limits, target_free will always be |
| // MaxFree(). |
| |
| // Test that we don't inflate the balloon if it's just a little bit. |
| { |
| const BalloonStats stats = { |
| {.free_memory = policy.MaxFree() + MiB(1), .disk_caches = 0}}; |
| EXPECT_EQ(0, policy.ComputeBalloonDeltaImpl(0 /* host_free */, stats, |
| 0 /* host_available */, false, |
| "test", 0, {})); |
| } |
| |
| // Test that we do inflate the balloon if it's a lot (twice MaxFree()). |
| { |
| const BalloonStats stats = { |
| {.free_memory = policy.MaxFree() * 2, .disk_caches = 0}}; |
| EXPECT_EQ(policy.MaxFree(), |
| policy.ComputeBalloonDeltaImpl(0 /* host_free */, stats, |
| 0 /* host_available */, false, |
| "test", 0, {})); |
| } |
| |
| // Test that we deflate the balloon even if we just need a small piece. |
| { |
| const BalloonStats stats = { |
| {.free_memory = policy.MaxFree() * 3 / 4, .disk_caches = 0}}; |
| EXPECT_EQ(-(policy.MaxFree() / 4), |
| policy.ComputeBalloonDeltaImpl(0 /* host_free */, stats, |
| 0 /* host_available */, false, |
| "test", 0, {})); |
| } |
| } |
| |
| // Tests that moderate_target_cache works as expected. |
| TEST(BalloonPolicyTest, LimitCacheModerate) { |
| // Values are roughly what a 4GB ARCVM would get (but rounded) |
| const int64_t host_lwm = MiB(200); |
| ZoneInfoStats guest_stats = {.sum_low = MiB(200), .totalreserve = MiB(300)}; |
| MemoryMargins margins = {.critical = MiB(400), .moderate = MiB(2000)}; |
| const LimitCacheBalloonPolicy::Params params = { |
| .reclaim_target_cache = 0, |
| .critical_target_cache = 0, |
| .moderate_target_cache = MiB(200)}; |
| const auto metrics_library = std::make_unique<MetricsLibraryMock>(); |
| const auto metrics = std::make_unique<mm::BalloonMetrics>( |
| apps::VmType::ARCVM, |
| raw_ref<MetricsLibraryInterface>::from_ptr(metrics_library.get())); |
| LimitCacheBalloonPolicy policy( |
| margins, host_lwm, guest_stats, params, "test", |
| raw_ref<mm::BalloonMetrics>::from_ptr(metrics.get())); |
| |
| // limit_start is the host_available level below which we start limiting |
| // guest memory. |
| const uint64_t limit_start = |
| margins.moderate + policy.MaxFree() - guest_stats.sum_low; |
| |
| // Test that we inflate the balloon a bit when we start getting a bit close |
| // to the moderate margin. |
| { |
| BalloonStats stats = {{ |
| .free_memory = policy.MaxFree(), |
| .disk_caches = MiB(1000), |
| }}; |
| EXPECT_EQ(MiB(1), policy.ComputeBalloonDeltaImpl( |
| 0 /* host_free */, stats, |
| limit_start - MiB(1) /* host_available */, false, |
| "test", 0, {})); |
| } |
| |
| // Test that when there is less cache left than the distance to target free, |
| // we only inflate the balloon enough to reclaim that cache. |
| { |
| BalloonStats stats = { |
| {.free_memory = policy.MaxFree(), .disk_caches = MiB(300)}}; |
| const int64_t cache_above_limit = |
| stats.stats_ffi.disk_caches - params.moderate_target_cache; |
| EXPECT_EQ(cache_above_limit, |
| policy.ComputeBalloonDeltaImpl( |
| 0 /* host_free */, stats, |
| margins.moderate /* host_available */, false, "test", 0, {})); |
| } |
| |
| // Test that when we are way below the moderate margin, we still give the |
| // guest MinFree() memory. |
| { |
| BalloonStats stats = { |
| {.free_memory = policy.MaxFree(), .disk_caches = MiB(2000)}}; |
| const int64_t free_above_min = |
| stats.stats_ffi.free_memory - policy.MinFree(); |
| EXPECT_EQ(free_above_min, |
| policy.ComputeBalloonDeltaImpl(0 /* host_free */, stats, |
| 0 /* host_available */, false, |
| "test", 0, {})); |
| } |
| } |
| |
| // Tests that critical_target_cache works as expected. |
| TEST(BalloonPolicyTest, LimitCacheCritical) { |
| // Values are roughly what a 4GB ARCVM would get (but rounded) |
| const int64_t host_lwm = MiB(200); |
| ZoneInfoStats guest_stats = {.sum_low = MiB(200), .totalreserve = MiB(300)}; |
| MemoryMargins margins = {.critical = MiB(400), .moderate = MiB(2000)}; |
| const LimitCacheBalloonPolicy::Params params = { |
| .reclaim_target_cache = 0, |
| .critical_target_cache = MiB(100), |
| .moderate_target_cache = 0}; |
| const auto metrics_library = std::make_unique<MetricsLibraryMock>(); |
| const auto metrics = std::make_unique<mm::BalloonMetrics>( |
| apps::VmType::ARCVM, |
| raw_ref<MetricsLibraryInterface>::from_ptr(metrics_library.get())); |
| LimitCacheBalloonPolicy policy( |
| margins, host_lwm, guest_stats, params, "test", |
| raw_ref<mm::BalloonMetrics>::from_ptr(metrics.get())); |
| |
| // limit_start is the host_available level below which we start limiting |
| // guest memory. |
| const uint64_t limit_start = |
| margins.critical + policy.MaxFree() - guest_stats.sum_low; |
| |
| // Test that we inflate the balloon a bit when we start getting a bit close |
| // to the critical margin. |
| { |
| BalloonStats stats = { |
| {.free_memory = policy.MaxFree(), .disk_caches = MiB(1000)}}; |
| EXPECT_EQ(MiB(1), policy.ComputeBalloonDeltaImpl( |
| 0 /* host_free */, stats, |
| limit_start - MiB(1) /* host_available */, false, |
| "test", 0, {})); |
| } |
| |
| // Test that when there is less cache left than the distance to target free, |
| // we only inflate the balloon enough to reclaim that cache. |
| { |
| BalloonStats stats = { |
| {.free_memory = policy.MaxFree(), .disk_caches = MiB(150)}}; |
| const int64_t cache_above_limit = |
| stats.stats_ffi.disk_caches - params.critical_target_cache; |
| EXPECT_EQ(cache_above_limit, |
| policy.ComputeBalloonDeltaImpl( |
| 0 /* host_free */, stats, |
| margins.critical /* host_available */, false, "test", 0, {})); |
| } |
| |
| // Test that when we are way below the critical margin, we still give the |
| // guest MinFree() memory. |
| { |
| BalloonStats stats = { |
| {.free_memory = policy.MaxFree(), .disk_caches = MiB(1000)}}; |
| const int64_t free_above_min = |
| stats.stats_ffi.free_memory - policy.MinFree(); |
| EXPECT_EQ(free_above_min, |
| policy.ComputeBalloonDeltaImpl(0 /* host_free */, stats, |
| 0 /* host_available */, false, |
| "test", 0, {})); |
| } |
| } |
| |
| // Tests that reclaim_target_cache works as expected. |
| TEST(BalloonPolicyTest, LimitCacheReclaim) { |
| // Values are roughly what a 4GB ARCVM would get (but rounded) |
| const int64_t host_lwm = MiB(200); |
| ZoneInfoStats guest_stats = {.sum_low = MiB(200), .totalreserve = MiB(300)}; |
| MemoryMargins margins = {.critical = MiB(400), .moderate = MiB(2000)}; |
| const LimitCacheBalloonPolicy::Params params = { |
| .reclaim_target_cache = MiB(100), |
| .critical_target_cache = 0, |
| .moderate_target_cache = 0}; |
| const auto metrics_library = std::make_unique<MetricsLibraryMock>(); |
| const auto metrics = std::make_unique<mm::BalloonMetrics>( |
| apps::VmType::ARCVM, |
| raw_ref<MetricsLibraryInterface>::from_ptr(metrics_library.get())); |
| LimitCacheBalloonPolicy policy( |
| margins, host_lwm, guest_stats, params, "test", |
| raw_ref<mm::BalloonMetrics>::from_ptr(metrics.get())); |
| |
| // limit_start is the host_free level below which we start limiting |
| // guest memory. |
| const uint64_t limit_start = |
| host_lwm + policy.MaxFree() - guest_stats.sum_low; |
| |
| // Test that we inflate the balloon a bit when we start getting a bit close |
| // to reclaiming in the host. |
| { |
| BalloonStats stats = { |
| {.free_memory = policy.MaxFree(), .disk_caches = MiB(1000)}}; |
| EXPECT_EQ(MiB(1), policy.ComputeBalloonDeltaImpl( |
| limit_start - MiB(1) /* host_free */, stats, |
| 0 /* host_available */, false, "test", 0, {})); |
| } |
| |
| // Test that when there is less cache left than the distance to target free, |
| // we only inflate the balloon enough to reclaim that cache. |
| { |
| BalloonStats stats = { |
| {.free_memory = policy.MaxFree(), .disk_caches = MiB(150)}}; |
| const int64_t cache_above_limit = |
| stats.stats_ffi.disk_caches - params.reclaim_target_cache; |
| EXPECT_EQ(cache_above_limit, |
| policy.ComputeBalloonDeltaImpl(host_lwm /* host_free */, stats, |
| 0 /* host_available */, false, |
| "test", 0, {})); |
| } |
| |
| // Test that when we are way past reclaiming in the host, we still give the |
| // guest MinFree() memory. |
| { |
| BalloonStats stats = { |
| {.free_memory = policy.MaxFree(), .disk_caches = MiB(1000)}}; |
| const int64_t free_above_min = |
| stats.stats_ffi.free_memory - policy.MinFree(); |
| EXPECT_EQ(free_above_min, |
| policy.ComputeBalloonDeltaImpl(0 /* host_free */, stats, |
| 0 /* host_available */, false, |
| "test", 0, {})); |
| } |
| } |
| |
| // Tests that critical_target_cache and moderate_target_cache work together as |
| // expected. |
| TEST(BalloonPolicyTest, LimitCacheModerateAndCritical) { |
| // Values are roughly what a 4GB ARCVM would get (but rounded) |
| const int64_t host_lwm = MiB(200); |
| ZoneInfoStats guest_stats = {.sum_low = MiB(200), .totalreserve = MiB(300)}; |
| MemoryMargins margins = {.critical = MiB(400), .moderate = MiB(2000)}; |
| const LimitCacheBalloonPolicy::Params params = { |
| .reclaim_target_cache = 0, |
| .critical_target_cache = MiB(100), |
| .moderate_target_cache = MiB(200)}; |
| const auto metrics_library = std::make_unique<MetricsLibraryMock>(); |
| const auto metrics = std::make_unique<mm::BalloonMetrics>( |
| apps::VmType::ARCVM, |
| raw_ref<MetricsLibraryInterface>::from_ptr(metrics_library.get())); |
| LimitCacheBalloonPolicy policy( |
| margins, host_lwm, guest_stats, params, "test", |
| raw_ref<mm::BalloonMetrics>::from_ptr(metrics.get())); |
| |
| // Test that when we are limited by both moderate and critical available cache |
| // limits, the smaller of the two is used. |
| BalloonStats stats = { |
| {.free_memory = policy.MaxFree(), .disk_caches = MiB(150)}}; |
| const int64_t cache_above_limit = |
| stats.stats_ffi.disk_caches - params.critical_target_cache; |
| EXPECT_EQ(cache_above_limit, |
| policy.ComputeBalloonDeltaImpl( |
| 0 /* host_free */, stats, margins.critical /* host_available */, |
| false, "test", 0, {})); |
| } |
| |
| // Tests that the guest gets MinFree memory even if the host is very low. |
| TEST(BalloonPolicyTest, LimitCacheGuestFreeLow) { |
| // Values are roughly what a 4GB ARCVM would get (but rounded) |
| const int64_t host_lwm = MiB(200); |
| ZoneInfoStats guest_stats = {.sum_low = MiB(200), .totalreserve = MiB(300)}; |
| MemoryMargins margins = {.critical = MiB(400), .moderate = MiB(2000)}; |
| const LimitCacheBalloonPolicy::Params params = { |
| .reclaim_target_cache = 0, |
| .critical_target_cache = MiB(100), |
| .moderate_target_cache = MiB(200)}; |
| const auto metrics_library = std::make_unique<MetricsLibraryMock>(); |
| const auto metrics = std::make_unique<mm::BalloonMetrics>( |
| apps::VmType::ARCVM, |
| raw_ref<MetricsLibraryInterface>::from_ptr(metrics_library.get())); |
| LimitCacheBalloonPolicy policy( |
| margins, host_lwm, guest_stats, params, "test", |
| raw_ref<mm::BalloonMetrics>::from_ptr(metrics.get())); |
| |
| BalloonStats stats = {{.free_memory = 0, .disk_caches = MiB(150)}}; |
| EXPECT_EQ(-policy.MinFree(), |
| policy.ComputeBalloonDeltaImpl(0 /* host_free */, stats, |
| 0 /* host_available */, false, |
| "test", 0, {})); |
| } |
| |
| // Tests that ParseZoneInfoStats works on real input. |
| TEST(BalloonPolicyTest, ParseZoneInfoStatsSnapshot) { |
| auto stats = ParseZoneInfoStats( |
| "Node 0, zone DMA\n" |
| " per-node stats\n" |
| " nr_inactive_anon 364023\n" |
| " nr_active_anon 97740\n" |
| " nr_inactive_file 20238\n" |
| " nr_active_file 95809\n" |
| " nr_unevictable 24263\n" |
| " nr_slab_reclaimable 7997\n" |
| " nr_slab_unreclaimable 18546\n" |
| " nr_isolated_anon 0\n" |
| " nr_isolated_file 0\n" |
| " workingset_nodes 1789\n" |
| " workingset_refault_anon 0\n" |
| " workingset_refault_file 86864\n" |
| " workingset_activate_anon 0\n" |
| " workingset_activate_file 13430\n" |
| " workingset_restore_anon 0\n" |
| " workingset_restore_file 72672\n" |
| " workingset_nodereclaim 0\n" |
| " nr_anon_pages 450240\n" |
| " nr_mapped 48448\n" |
| " nr_file_pages 140275\n" |
| " nr_dirty 0\n" |
| " nr_writeback 0\n" |
| " nr_writeback_temp 0\n" |
| " nr_shmem 23504\n" |
| " nr_shmem_hugepages 0\n" |
| " nr_shmem_pmdmapped 0\n" |
| " nr_file_hugepages 0\n" |
| " nr_file_pmdmapped 0\n" |
| " nr_anon_transparent_hugepages 123\n" |
| " nr_vmscan_write 0\n" |
| " nr_vmscan_immediate_reclaim 0\n" |
| " nr_dirtied 95963\n" |
| " nr_written 95960\n" |
| " nr_kernel_misc_reclaimable 0\n" |
| " nr_foll_pin_acquired 392\n" |
| " nr_foll_pin_released 392\n" |
| " nr_kernel_stack 17440\n" |
| " pages free 3208\n" |
| " min 113\n" |
| " low 151\n" |
| " high 179\n" |
| " spanned 4095\n" |
| " present 3742\n" |
| " managed 3208\n" |
| " cma 0\n" |
| " protection: (0, 3248, 3700, 3700, 3700)\n" |
| " nr_free_pages 3208\n" |
| " nr_zone_inactive_anon 0\n" |
| " nr_zone_active_anon 0\n" |
| " nr_zone_inactive_file 0\n" |
| " nr_zone_active_file 0\n" |
| " nr_zone_unevictable 0\n" |
| " nr_zone_write_pending 0\n" |
| " nr_mlock 0\n" |
| " nr_page_table_pages 0\n" |
| " nr_bounce 0\n" |
| " nr_zspages 0\n" |
| " nr_free_cma 0\n" |
| " pagesets\n" |
| " cpu: 0\n" |
| " count: 0\n" |
| " high: 0\n" |
| " batch: 1\n" |
| " vm stats threshold: 4\n" |
| " cpu: 1\n" |
| " count: 0\n" |
| " high: 0\n" |
| " batch: 1\n" |
| " vm stats threshold: 4\n" |
| " cpu: 2\n" |
| " count: 0\n" |
| " high: 0\n" |
| " batch: 1\n" |
| " vm stats threshold: 4\n" |
| " node_unreclaimable: 0\n" |
| " start_pfn: 1\n" |
| "Node 0, zone DMA32\n" |
| " pages free 55144\n" |
| " min 29527\n" |
| " low 39744\n" |
| " high 47125\n" |
| " spanned 1044480\n" |
| " present 847872\n" |
| " managed 831488\n" |
| " cma 0\n" |
| " protection: (0, 0, 452, 452, 452)\n" |
| " nr_free_pages 55144\n" |
| " nr_zone_inactive_anon 299032\n" |
| " nr_zone_active_anon 87931\n" |
| " nr_zone_inactive_file 19179\n" |
| " nr_zone_active_file 86754\n" |
| " nr_zone_unevictable 20737\n" |
| " nr_zone_write_pending 0\n" |
| " nr_mlock 21\n" |
| " nr_page_table_pages 7964\n" |
| " nr_bounce 0\n" |
| " nr_zspages 0\n" |
| " nr_free_cma 0\n" |
| " pagesets\n" |
| " cpu: 0\n" |
| " count: 58\n" |
| " high: 378\n" |
| " batch: 63\n" |
| " vm stats threshold: 24\n" |
| " cpu: 1\n" |
| " count: 95\n" |
| " high: 378\n" |
| " batch: 63\n" |
| " vm stats threshold: 24\n" |
| " cpu: 2\n" |
| " count: 0\n" |
| " high: 378\n" |
| " batch: 63\n" |
| " vm stats threshold: 24\n" |
| " node_unreclaimable: 0\n" |
| " start_pfn: 4096\n" |
| "Node 0, zone Normal\n" |
| " pages free 7002\n" |
| " min 4150\n" |
| " low 5586\n" |
| " high 6623\n" |
| " spanned 141824\n" |
| " present 141824\n" |
| " managed 116890\n" |
| " cma 0\n" |
| " protection: (0, 0, 0, 0, 0)\n" |
| " nr_free_pages 7002\n" |
| " nr_zone_inactive_anon 64991\n" |
| " nr_zone_active_anon 9801\n" |
| " nr_zone_inactive_file 1059\n" |
| " nr_zone_active_file 9055\n" |
| " nr_zone_unevictable 3526\n" |
| " nr_zone_write_pending 0\n" |
| " nr_mlock 1892\n" |
| " nr_page_table_pages 839\n" |
| " nr_bounce 0\n" |
| " nr_zspages 0\n" |
| " nr_free_cma 0\n" |
| " pagesets\n" |
| " cpu: 0\n" |
| " count: 41\n" |
| " high: 186\n" |
| " batch: 31\n" |
| " vm stats threshold: 12\n" |
| " cpu: 1\n" |
| " count: 7\n" |
| " high: 186\n" |
| " batch: 31\n" |
| " vm stats threshold: 12\n" |
| " cpu: 2\n" |
| " count: 0\n" |
| " high: 186\n" |
| " batch: 31\n" |
| " vm stats threshold: 12\n" |
| " node_unreclaimable: 0\n" |
| " start_pfn: 1048576\n" |
| "Node 0, zone Movable\n" |
| " pages free 0\n" |
| " min 0\n" |
| " low 0\n" |
| " high 0\n" |
| " spanned 0\n" |
| " present 0\n" |
| " managed 0\n" |
| " cma 0\n" |
| " protection: (0, 0, 0, 0, 0)\n" |
| "Node 0, zone Device\n" |
| " pages free 0\n" |
| " min 0\n" |
| " low 0\n" |
| " high 0\n" |
| " spanned 0\n" |
| " present 0\n" |
| " managed 0\n" |
| " cma 0\n" |
| " protection: (0, 0, 0, 0, 0)\n"); |
| |
| EXPECT_TRUE(stats); |
| EXPECT_EQ(stats->sum_low, 45481 * PAGE_BYTES); |
| EXPECT_EQ(stats->totalreserve, 85041 * PAGE_BYTES); |
| } |
| |
| // Tests that ParseZoneInfoStats works on real input. |
| TEST(BalloonPolicyTest, ParseZoneInfoStatsFailures) { |
| EXPECT_FALSE(ParseZoneInfoStats("")); |
| |
| // Missing non-zero high and protection. |
| EXPECT_FALSE(ParseZoneInfoStats("low 1")); |
| |
| // Missing protection. |
| EXPECT_FALSE(ParseZoneInfoStats("low 1\nhigh 1")); |
| |
| // Bad low watermark. |
| EXPECT_FALSE(ParseZoneInfoStats("low 1a\nhigh 1\nprotection(1)")); |
| |
| // Bad low watermark. |
| EXPECT_FALSE(ParseZoneInfoStats("low 1 1\nhigh 1\nprotection(1)")); |
| |
| // Bad high watermark. |
| EXPECT_FALSE(ParseZoneInfoStats("low 1\nhigh a1\nprotection(1)")); |
| |
| // Bad high watermark. |
| EXPECT_FALSE(ParseZoneInfoStats("low 1\nhigh 2 2\nprotection(1)")); |
| |
| // Missing low. |
| EXPECT_FALSE(ParseZoneInfoStats("high 1\nprotection: (1)")); |
| |
| // Missing high before protection. |
| EXPECT_FALSE(ParseZoneInfoStats("low 1\nprotection: (1)")); |
| |
| // Missing high before protection. |
| EXPECT_FALSE( |
| ParseZoneInfoStats("low 1\nhigh 1\nprotection: (1)\nprotection: (1)")); |
| |
| // No protection line between two high lines. |
| EXPECT_FALSE(ParseZoneInfoStats("low 1\nhigh 1\nhigh: 1")); |
| } |
| |
| TEST(BalloonPolicyTest, LimitCacheBalloonDeflationLimits) { |
| const auto metrics_library = std::make_unique<MetricsLibraryMock>(); |
| const auto metrics = std::make_unique<mm::BalloonMetrics>( |
| apps::VmType::ARCVM, |
| raw_ref<MetricsLibraryInterface>::from_ptr(metrics_library.get())); |
| LimitCacheBalloonPolicy policy( |
| {}, 0, {}, {.responsive_max_deflate_bytes = 200 * 4096}, "test", |
| raw_ref<mm::BalloonMetrics>::from_ptr(metrics.get())); |
| ComponentMemoryMargins margins{ |
| .chrome_critical = 0, |
| .chrome_moderate = 0, |
| .arcvm_foreground = 300 * 4096, |
| .arcvm_perceptible = 600 * 4096, |
| .arcvm_cached = 800 * 4096, |
| }; |
| policy.UpdateBalloonDeflationLimits(margins, |
| /* total_available */ 1000 * 4096, |
| /* balloon_size */ 800 * 4096); |
| |
| // Should result in limits of: |
| // foreground, 100 * 4096 |
| // perceptible, 400 * 4096 |
| // cached, 600 * 4096 |
| |
| uint64_t new_balloon_size = 0; |
| uint64_t freed_space = 0; |
| |
| ASSERT_TRUE(policy.DeflateBalloonToSaveProcess( |
| 700 * 4096, kAppAdjForegroundMax, new_balloon_size, freed_space)); |
| // Should be deflated by max_deflate_bytes |
| ASSERT_EQ(new_balloon_size, 600 * 4096); |
| ASSERT_EQ(freed_space, 200 * 4096); |
| |
| // Should not be deflated for oom score of cached since the |
| // new size is already at the limit for cached |
| ASSERT_FALSE(policy.DeflateBalloonToSaveProcess( |
| 1, kAppAdjPerceptibleMax + 1, new_balloon_size, freed_space)); |
| ASSERT_FALSE(policy.DeflateBalloonToSaveProcess( |
| 1, kAppAdjCachedMax, new_balloon_size, freed_space)); |
| |
| // Should be deflated for perceptible by max_deflate down to the limit for |
| // perceptible |
| ASSERT_TRUE(policy.DeflateBalloonToSaveProcess( |
| 500 * 4096, kAppAdjPerceptibleMax, new_balloon_size, freed_space)); |
| ASSERT_EQ(new_balloon_size, 400 * 4096); |
| ASSERT_EQ(freed_space, 200 * 4096); |
| |
| // Should no longer be deflated for perceptible since the limit has been |
| // reached |
| ASSERT_FALSE(policy.DeflateBalloonToSaveProcess( |
| 1, kAppAdjPerceptibleMax, new_balloon_size, freed_space)); |
| ASSERT_FALSE(policy.DeflateBalloonToSaveProcess( |
| 1, kAppAdjForegroundMax + 1, new_balloon_size, freed_space)); |
| |
| // Should still be deflated for foreground |
| ASSERT_TRUE(policy.DeflateBalloonToSaveProcess( |
| 150 * 4096, kAppAdjForegroundMax, new_balloon_size, freed_space)); |
| ASSERT_EQ(new_balloon_size, 250 * 4096); |
| ASSERT_EQ(freed_space, 150 * 4096); |
| |
| // Should not be deflated for foreground if the app and max deflate are both |
| // too large |
| ASSERT_FALSE(policy.DeflateBalloonToSaveProcess( |
| 300 * 4096, kAppAdjForegroundMax - 1, new_balloon_size, freed_space)); |
| ASSERT_FALSE(policy.DeflateBalloonToSaveProcess( |
| 151 * 4096, kAppAdjForegroundMax - 1, new_balloon_size, freed_space)); |
| |
| // Should still be deflated for foreground if the app is small enough |
| ASSERT_TRUE(policy.DeflateBalloonToSaveProcess( |
| 150 * 4096, kAppAdjForegroundMax, new_balloon_size, freed_space)); |
| ASSERT_EQ(new_balloon_size, 100 * 4096); |
| ASSERT_EQ(freed_space, 150 * 4096); |
| |
| // At the lowest limit, should not be deflated for anything |
| ASSERT_FALSE(policy.DeflateBalloonToSaveProcess( |
| 1, kAppAdjForegroundMax - 1, new_balloon_size, freed_space)); |
| } |
| |
| // Test that SumWorkingSets properly adds WorkingSet bins. |
| TEST(BalloonPolicyTest, BalloonWorkingSetSum) { |
| uint64_t actual = 0; |
| WorkingSetBucketFfi bins1[BalloonWorkingSet::kWorkingSetNumBins]; |
| for (unsigned i = 0; i < BalloonWorkingSet::kWorkingSetNumBins; ++i) { |
| bins1[i] = {0, {250 * i + 1, 300 * i + 3}}; |
| } |
| BalloonWSFfi ffi1; |
| for (unsigned i = 0; i < BalloonWorkingSet::kWorkingSetNumBins; ++i) { |
| ffi1.ws[i] = bins1[i]; |
| } |
| |
| WorkingSetBucketFfi bins2[BalloonWorkingSet::kWorkingSetNumBins]; |
| for (unsigned i = 0; i < BalloonWorkingSet::kWorkingSetNumBins; ++i) { |
| bins2[i] = {0, {43 * i, 44 * i}}; |
| } |
| |
| BalloonWSFfi ffi2; |
| for (unsigned i = 0; i < BalloonWorkingSet::kWorkingSetNumBins; ++i) { |
| ffi2.ws[i] = bins2[i]; |
| } |
| |
| BalloonWorkingSet ws1 = {ffi1, actual}; |
| BalloonWorkingSet ws2 = {ffi2, actual}; |
| BalloonWorkingSet result = SumWorkingSets(ws1, ws2); |
| |
| // Assert that the result working set is the sum of the |
| // ws1 and ws2. |
| for (unsigned i = 0; i < BalloonWorkingSet::kWorkingSetNumBins; ++i) { |
| ASSERT_EQ(result.working_set_ffi.ws[i].bytes[0], 293 * i + 1); |
| ASSERT_EQ(result.working_set_ffi.ws[i].bytes[1], 344 * i + 3); |
| } |
| } |
| |
| } // namespace concierge |
| } // namespace vm_tools |