| // Copyright 2021 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/memory/memory_ablation_study.h" |
| |
| #include <algorithm> |
| #include <cstring> |
| |
| #include "base/command_line.h" |
| #include "base/debug/alias.h" |
| #include "base/feature_list.h" |
| #include "base/metrics/field_trial_params.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/system/sys_info.h" |
| #include "base/timer/timer.h" |
| #include "build/build_config.h" |
| #include "crypto/random.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| |
| namespace memory { |
| |
| const char kUXStudy1Switch[] = "ux-study-1"; |
| const char kUXStudy1A[] = "A"; |
| const char kUXStudy1B[] = "B"; |
| const char kUXStudy1C[] = "C"; |
| const char kUXStudy1D[] = "D"; |
| |
| namespace { |
| |
| // These values are logged to UMA. Entries should not be renumbered and numeric |
| // values should never be reused. Please keep in sync with "UXStudy1Arm" in |
| // src/tools/metrics/histograms/enums.xml. |
| enum class UXStudy1Arm { |
| A = 0, |
| B = 1, |
| C = 2, |
| D = 3, |
| kMaxValue = D, |
| }; |
| |
| // The name of the Finch study that turns on the experiment. |
| const base::Feature kMemoryAblationStudy{"MemoryAblationStudy", |
| base::FEATURE_DISABLED_BY_DEFAULT}; |
| |
| // The total amount of memory to ablate in MB. |
| const char kAblationSizeMb[] = "ablation-size-mb"; |
| |
| // Number of seconds to wait between allocation periods. |
| constexpr int kAllocateTimerIntervalSeconds = 10; |
| |
| // Maximum number of MB to allocate at a time. |
| constexpr int kAllocateAmountMb = 10; |
| |
| // Numbers of seconds to wait between reading the next region. |
| constexpr int kReadTimerIntervalSeconds = 30; |
| |
| // Size in bytes of the uncompressible region. |
| constexpr int kUncompressibleRegionSize = 4096; |
| |
| // Theres some variance on exact ram values so we use values slightly under 2GB |
| // and 4GB. |
| #if BUILDFLAG(IS_ANDROID) |
| constexpr int kMinimumRamMB = 1700; |
| #else |
| constexpr int kMinimumRamMB = 3700; |
| #endif |
| |
| // The forced amount to ablate in MB, set by command line. |
| absl::optional<int32_t> ForcedAblationMB() { |
| base::CommandLine* line = base::CommandLine::ForCurrentProcess(); |
| std::string value = line->GetSwitchValueASCII(kUXStudy1Switch); |
| |
| int32_t ablation = 0; |
| UXStudy1Arm study = UXStudy1Arm::A; |
| if (value == kUXStudy1A) { |
| ablation = 0; |
| study = UXStudy1Arm::A; |
| } else if (value == kUXStudy1B) { |
| ablation = 300; |
| study = UXStudy1Arm::B; |
| } else if (value == kUXStudy1C) { |
| ablation = 700; |
| study = UXStudy1Arm::C; |
| } else if (value == kUXStudy1D) { |
| ablation = 1000; |
| study = UXStudy1Arm::D; |
| } else { |
| return absl::nullopt; |
| } |
| UMA_HISTOGRAM_ENUMERATION("Memory.UX.Study.1", study); |
| return ablation; |
| } |
| |
| } // namespace |
| |
| MemoryAblationStudy::MemoryAblationStudy() { |
| absl::optional<int32_t> forced_ablation = ForcedAblationMB(); |
| if (forced_ablation) { |
| remaining_allocation_mb_ = forced_ablation.value(); |
| } else { |
| // On Android we restrict to 2GB+ devices. |
| // On Desktop we restrict to 4GB+ devices. |
| if (base::SysInfo::AmountOfPhysicalMemoryMB() < kMinimumRamMB) |
| return; |
| |
| // This class does nothing if the study is disabled. |
| if (!base::FeatureList::IsEnabled(kMemoryAblationStudy)) { |
| return; |
| } |
| |
| remaining_allocation_mb_ = base::GetFieldTrialParamByFeatureAsInt( |
| kMemoryAblationStudy, kAblationSizeMb, /*default_value=*/0); |
| } |
| if (remaining_allocation_mb_ <= 0) |
| return; |
| |
| allocate_timer_.Start(FROM_HERE, base::Seconds(kAllocateTimerIntervalSeconds), |
| base::BindRepeating(&MemoryAblationStudy::Allocate, |
| base::Unretained(this))); |
| read_timer_.Start( |
| FROM_HERE, base::Seconds(kReadTimerIntervalSeconds), |
| base::BindRepeating(&MemoryAblationStudy::Read, base::Unretained(this))); |
| } |
| |
| MemoryAblationStudy::~MemoryAblationStudy() { |
| // Avoid compiler optimizations by aliasing |dummy_read_|. |
| uint8_t dummy = dummy_read_; |
| base::debug::Alias(&dummy); |
| } |
| |
| void MemoryAblationStudy::Allocate() { |
| CHECK_GT(remaining_allocation_mb_, 0); |
| int32_t amount_to_allocate_mb = |
| std::min(remaining_allocation_mb_, kAllocateAmountMb); |
| |
| // Do accounting first. Stop the timer if this is the last allocation. |
| remaining_allocation_mb_ -= amount_to_allocate_mb; |
| if (remaining_allocation_mb_ <= 0) { |
| allocate_timer_.Stop(); |
| } |
| |
| // Generate the initial uncompressible region if necessary. |
| if (uncompressible_region_.empty()) { |
| uncompressible_region_.resize(kUncompressibleRegionSize); |
| crypto::RandBytes(uncompressible_region_.data(), kUncompressibleRegionSize); |
| } |
| |
| // Allocate the new region. |
| size_t amount_to_allocate_bytes = amount_to_allocate_mb * 1024 * 1024; |
| Region region; |
| region.resize(amount_to_allocate_bytes); |
| |
| // Fill it with uncompressible bytes. |
| DCHECK_EQ(amount_to_allocate_bytes % kUncompressibleRegionSize, 0u); |
| uint8_t* ptr = region.data(); |
| for (size_t offset = 0; offset < amount_to_allocate_bytes; |
| offset += kUncompressibleRegionSize) { |
| memcpy(ptr + offset, uncompressible_region_.data(), |
| kUncompressibleRegionSize); |
| } |
| |
| regions_.push_back(std::move(region)); |
| } |
| |
| void MemoryAblationStudy::Read() { |
| if (regions_.empty()) |
| return; |
| |
| last_region_read_ = ((last_region_read_ + 1) % regions_.size()); |
| Region& region = regions_[last_region_read_]; |
| uint8_t* ptr = region.data(); |
| for (size_t offset = 0; offset < region.size(); |
| offset += kUncompressibleRegionSize) { |
| dummy_read_ += ptr[offset]; |
| } |
| } |
| |
| } // namespace memory |