blob: 855e8ade4e3c9e12d5a68378611fa1c7b47204d9 [file] [log] [blame]
/*
* Copyright 2017 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "cobalt/browser/memory_settings/auto_mem.h"
#include <algorithm>
#include <cmath>
#include <string>
#include <vector>
#include "base/optional.h"
#include "base/stl_util.h"
#include "base/string_number_conversions.h"
#include "base/string_split.h"
#include "base/stringprintf.h"
#include "cobalt/browser/memory_settings/auto_mem_settings.h"
#include "cobalt/browser/memory_settings/calculations.h"
#include "cobalt/browser/memory_settings/constants.h"
#include "cobalt/browser/memory_settings/constrainer.h"
#include "cobalt/browser/memory_settings/memory_settings.h"
#include "cobalt/browser/memory_settings/pretty_print.h"
#include "cobalt/browser/memory_settings/scaling_function.h"
#include "cobalt/browser/switches.h"
#include "cobalt/math/clamp.h"
#include "nb/lexical_cast.h"
namespace cobalt {
namespace browser {
namespace memory_settings {
namespace {
// Determines if the value signals "autoset".
template <typename ValueType>
bool SignalsAutoset(const ValueType& value) {
return (value < 0);
}
template <>
bool SignalsAutoset(const TextureDimensions& value) {
return value.IsAutoset();
}
template <typename MemorySettingType, typename ValueType>
void SetMemorySetting(const base::optional<ValueType>& command_line_setting,
const base::optional<ValueType>& build_setting,
const ValueType& autoset_value,
MemorySettingType* setting) {
const std::string setting_name = setting->name();
// True when the command line explicitly requests the variable to be autoset.
bool force_autoset = false;
// The value is set according to importance:
// 1) Command line switches are the most important, so set those if they
// exist.
if (command_line_setting) {
if (!SignalsAutoset(*command_line_setting)) {
setting->set_value(MemorySetting::kCmdLine, *command_line_setting);
return;
}
force_autoset = true;
}
// 2) Is there a build setting? Then set to build_setting, unless the command
// line specifies that it should be autoset.
if (build_setting && !force_autoset) {
setting->set_value(MemorySetting::kBuildSetting, *build_setting);
} else {
// 3) Otherwise bind to the autoset_value.
setting->set_value(MemorySetting::kAutoSet, autoset_value);
}
}
// Creates the specified memory setting type and binds it to (1) command line or
// else (2) build setting or else (3) an auto_set value.
template <typename MemorySettingType, typename ValueType>
scoped_ptr<MemorySettingType> CreateMemorySetting(
const char* setting_name,
const base::optional<ValueType>& command_line_setting,
const base::optional<ValueType>& build_setting,
const ValueType& autoset_value) {
scoped_ptr<MemorySettingType> output(new MemorySettingType(setting_name));
SetMemorySetting(command_line_setting, build_setting, autoset_value,
output.get());
return output.Pass();
}
scoped_ptr<IntSetting> CreateSystemMemorySetting(
const char* setting_name, MemorySetting::MemoryType memory_type,
const base::optional<int64_t>& command_line_setting,
const base::optional<int64_t>& build_setting,
const base::optional<int64_t>& starboard_value) {
scoped_ptr<IntSetting> setting(new IntSetting(setting_name));
setting->set_memory_type(memory_type);
if (command_line_setting) {
setting->set_value(MemorySetting::kCmdLine, *command_line_setting);
return setting.Pass();
}
if (build_setting) {
setting->set_value(MemorySetting::kBuildSetting, *build_setting);
return setting.Pass();
}
if (starboard_value) {
setting->set_value(MemorySetting::kStarboardAPI, *starboard_value);
return setting.Pass();
}
// This will mark the value as invalid.
setting->set_value(MemorySetting::kUnset, -1);
return setting.Pass();
}
void EnsureValuePositive(IntSetting* setting) {
if (!setting->valid()) {
return;
}
if (setting->value() < 0) {
setting->set_value(setting->source_type(), 0);
}
}
void EnsureValuePositive(DimensionSetting* setting) {
if (!setting->valid()) {
return;
}
const TextureDimensions value = setting->value();
if (value.width() < 0 || value.height() < 0 || value.bytes_per_pixel() < 0) {
setting->set_value(setting->source_type(), TextureDimensions());
}
}
void EnsureTwoBytesPerPixel(DimensionSetting* setting) {
if (!setting->valid()) {
return;
}
TextureDimensions value = setting->value();
if (value.bytes_per_pixel() != 2) {
LOG(ERROR) << "Only two bytes per pixel are allowed for setting: "
<< setting->name();
value.set_bytes_per_pixel(2);
setting->set_value(setting->source_type(), value);
}
}
// Sums up the memory consumption of all memory settings with of the input
// memory type.
int64_t SumMemoryConsumption(
MemorySetting::MemoryType memory_type_filter,
const std::vector<const MemorySetting*>& memory_settings) {
int64_t sum = 0;
for (size_t i = 0; i < memory_settings.size(); ++i) {
const MemorySetting* setting = memory_settings[i];
if (memory_type_filter == setting->memory_type()) {
sum += setting->MemoryConsumption();
}
}
return sum;
}
// Creates the GPU setting.
// This setting is unique because it may not be defined by command line, or
// build. In this was, it can be unset.
scoped_ptr<IntSetting> CreateGpuSetting(
const AutoMemSettings& command_line_settings,
const AutoMemSettings& build_settings) {
// Bind to the starboard api, if applicable.
base::optional<int64_t> starboard_setting;
if (SbSystemHasCapability(kSbSystemCapabilityCanQueryGPUMemoryStats)) {
starboard_setting = SbSystemGetTotalGPUMemory();
}
scoped_ptr<IntSetting> gpu_setting = CreateSystemMemorySetting(
switches::kMaxCobaltGpuUsage, MemorySetting::kGPU,
command_line_settings.max_gpu_in_bytes, build_settings.max_gpu_in_bytes,
starboard_setting);
EnsureValuePositive(gpu_setting.get());
return gpu_setting.Pass();
}
scoped_ptr<IntSetting> CreateCpuSetting(
const AutoMemSettings& command_line_settings,
const AutoMemSettings& build_settings) {
scoped_ptr<IntSetting> cpu_setting = CreateSystemMemorySetting(
switches::kMaxCobaltCpuUsage, MemorySetting::kCPU,
command_line_settings.max_cpu_in_bytes, build_settings.max_cpu_in_bytes,
SbSystemGetTotalCPUMemory());
EnsureValuePositive(cpu_setting.get());
return cpu_setting.Pass();
}
void CheckConstrainingValues(const MemorySetting& memory_setting) {
const size_t kNumTestPoints = 10;
std::vector<double> values;
for (size_t i = 0; i < kNumTestPoints; ++i) {
const double requested_constraining_value =
static_cast<double>(i) / static_cast<double>(kNumTestPoints - 1);
const double actual_constraining_value =
memory_setting.ComputeAbsoluteMemoryScale(
requested_constraining_value);
values.push_back(actual_constraining_value);
}
DCHECK(base::STLIsSorted(values))
<< "Constrainer in " << memory_setting.name() << " does not produce "
"monotonically decreasing values as input goes from 1.0 -> 0.0";
}
int64_t GenerateTargetMemoryBytes(
int64_t max_memory_bytes,
int64_t current_memory_bytes,
base::optional<int64_t> reduce_memory_bytes) {
// Make sure values are sanitized.
max_memory_bytes = std::max<int64_t>(0, max_memory_bytes);
current_memory_bytes = std::max<int64_t>(0, current_memory_bytes);
// If reduce_memory_bytes is valid and it's a zero or positive value then
// this is a signal that the calculation should be based off of this setting.
bool use_reduce_memory_input =
(reduce_memory_bytes && (-1 < *reduce_memory_bytes));
if (use_reduce_memory_input) {
// If reducing_memory_bytes is set exactly to 0, then this
// this will disable max_memory_bytes setting. current_memory_bytes
// will be returned as the target memory consumption,
// which will prevent memory constraining.
if (*reduce_memory_bytes == 0) {
return current_memory_bytes;
} else {
// Reduce memory bytes will subtract from the current memory
// consumption.
const int64_t target_value = current_memory_bytes - *reduce_memory_bytes;
return math::Clamp<int64_t>(target_value, 0, std::abs(target_value));
}
} else { // reduce_memory_bytes is not used. Use max_memory_bytes instead.
// max_memory_bytes == 0 is special, and signals that no constraining
// should happen.
if (max_memory_bytes == 0) {
return current_memory_bytes;
} else {
// A non-zero value means that max_memory_bytes is valid and should
// be used as the target value.
return max_memory_bytes;
}
}
}
} // namespace
AutoMem::AutoMem(const math::Size& ui_resolution,
const AutoMemSettings& command_line_settings,
const AutoMemSettings& build_settings) {
ConstructSettings(ui_resolution, command_line_settings, build_settings);
const int64_t target_cpu_memory =
GenerateTargetMemoryBytes(max_cpu_bytes_->value(),
SumAllMemoryOfType(MemorySetting::kCPU),
reduced_cpu_bytes_->optional_value());
const int64_t target_gpu_memory =
GenerateTargetMemoryBytes(max_gpu_bytes_->value(),
SumAllMemoryOfType(MemorySetting::kGPU),
reduced_gpu_bytes_->optional_value());
std::vector<MemorySetting*> memory_settings = AllMemorySettingsMutable();
ConstrainToMemoryLimits(target_cpu_memory,
target_gpu_memory,
&memory_settings,
&error_msgs_);
}
AutoMem::~AutoMem() {}
const IntSetting* AutoMem::misc_cobalt_cpu_size_in_bytes() const {
return misc_cobalt_cpu_size_in_bytes_.get();
}
const IntSetting* AutoMem::misc_cobalt_gpu_size_in_bytes() const {
return misc_cobalt_gpu_size_in_bytes_.get();
}
const IntSetting* AutoMem::remote_typeface_cache_size_in_bytes() const {
return remote_typeface_cache_size_in_bytes_.get();
}
const IntSetting* AutoMem::image_cache_size_in_bytes() const {
return image_cache_size_in_bytes_.get();
}
const IntSetting* AutoMem::javascript_gc_threshold_in_bytes() const {
return javascript_gc_threshold_in_bytes_.get();
}
const DimensionSetting* AutoMem::skia_atlas_texture_dimensions() const {
return skia_atlas_texture_dimensions_.get();
}
const IntSetting* AutoMem::skia_cache_size_in_bytes() const {
return skia_cache_size_in_bytes_.get();
}
const IntSetting* AutoMem::software_surface_cache_size_in_bytes() const {
return software_surface_cache_size_in_bytes_.get();
}
const IntSetting* AutoMem::offscreen_target_cache_size_in_bytes() const {
return offscreen_target_cache_size_in_bytes_.get();
}
const IntSetting* AutoMem::max_cpu_bytes() const {
return max_cpu_bytes_.get();
}
const IntSetting* AutoMem::max_gpu_bytes() const {
return max_gpu_bytes_.get();
}
std::vector<const MemorySetting*> AutoMem::AllMemorySettings() const {
AutoMem* this_unconst = const_cast<AutoMem*>(this);
std::vector<MemorySetting*> all_settings_mutable =
this_unconst->AllMemorySettingsMutable();
std::vector<const MemorySetting*> all_settings;
all_settings.assign(all_settings_mutable.begin(),
all_settings_mutable.end());
return all_settings;
}
// Make sure that this is the same as AllMemorySettings().
std::vector<MemorySetting*> AutoMem::AllMemorySettingsMutable() {
std::vector<MemorySetting*> all_settings;
// Keep these in alphabetical order.
all_settings.push_back(image_cache_size_in_bytes_.get());
all_settings.push_back(javascript_gc_threshold_in_bytes_.get());
all_settings.push_back(misc_cobalt_cpu_size_in_bytes_.get());
all_settings.push_back(misc_cobalt_gpu_size_in_bytes_.get());
all_settings.push_back(offscreen_target_cache_size_in_bytes_.get());
all_settings.push_back(remote_typeface_cache_size_in_bytes_.get());
all_settings.push_back(skia_atlas_texture_dimensions_.get());
all_settings.push_back(skia_cache_size_in_bytes_.get());
all_settings.push_back(software_surface_cache_size_in_bytes_.get());
return all_settings;
}
std::string AutoMem::ToPrettyPrintString(bool use_color_ascii) const {
std::stringstream ss;
ss << "AutoMem:\n\n";
std::vector<const MemorySetting*> all_settings = AllMemorySettings();
ss << GeneratePrettyPrintTable(use_color_ascii, all_settings) << "\n";
int64_t cpu_consumption =
SumMemoryConsumption(MemorySetting::kCPU, all_settings);
int64_t gpu_consumption =
SumMemoryConsumption(MemorySetting::kGPU, all_settings);
ss << GenerateMemoryTable(use_color_ascii,
*max_cpu_bytes_, *max_gpu_bytes_,
cpu_consumption, gpu_consumption);
// Copy strings and optionally add more.
std::vector<std::string> error_msgs = error_msgs_;
if (max_cpu_bytes_->value() <= 0) {
error_msgs.push_back("ERROR - max_cobalt_cpu_usage WAS 0 BYTES.");
} else if (cpu_consumption > max_cpu_bytes_->value()) {
error_msgs.push_back("ERROR - CPU CONSUMED WAS MORE THAN AVAILABLE.");
}
const base::optional<int64_t> max_gpu_value =
max_gpu_bytes_->optional_value();
if (max_gpu_value) {
if (*max_gpu_value <= 0) {
error_msgs.push_back("ERROR - max_cobalt_gpu_usage WAS 0 BYTES.");
} else if (gpu_consumption > *max_gpu_value) {
error_msgs.push_back("ERROR - GPU CONSUMED WAS MORE THAN AVAILABLE.");
}
}
// Stringify error messages.
if (!error_msgs.empty()) {
std::stringstream ss_error;
ss_error << "AutoMem had errors:\n";
for (size_t i = 0; i < error_msgs.size(); ++i) {
ss_error << " " << error_msgs[i] << "\n\n";
}
ss_error << "Please see cobalt/docs/memory_tuning.md "
"for more information.";
ss << MakeBorder(ss_error.str(), '*');
}
std::string output_str = ss.str();
return output_str;
}
int64_t AutoMem::SumAllMemoryOfType(
MemorySetting::MemoryType memory_type) const {
return SumMemoryConsumption(memory_type, AllMemorySettings());
}
void AutoMem::ConstructSettings(const math::Size& ui_resolution,
const AutoMemSettings& command_line_settings,
const AutoMemSettings& build_settings) {
max_cpu_bytes_ = CreateCpuSetting(command_line_settings, build_settings);
max_gpu_bytes_ = CreateGpuSetting(command_line_settings, build_settings);
reduced_cpu_bytes_ = CreateSystemMemorySetting(
switches::kReduceCpuMemoryBy, MemorySetting::kCPU,
command_line_settings.reduce_cpu_memory_by,
build_settings.reduce_cpu_memory_by, -1);
if (reduced_cpu_bytes_->value() == -1) {
// This effectively disables the value from being used in the constrainer.
reduced_cpu_bytes_->set_value(MemorySetting::kUnset, 0);
}
reduced_gpu_bytes_ = CreateSystemMemorySetting(
switches::kReduceGpuMemoryBy, MemorySetting::kGPU,
command_line_settings.reduce_gpu_memory_by,
build_settings.reduce_gpu_memory_by, -1);
if (reduced_cpu_bytes_->value() == -1) {
// This effectively disables the value from being used in the constrainer.
reduced_gpu_bytes_->set_value(MemorySetting::kUnset, 0);
}
// Set the ImageCache
image_cache_size_in_bytes_ = CreateMemorySetting<IntSetting, int64_t>(
switches::kImageCacheSizeInBytes,
command_line_settings.cobalt_image_cache_size_in_bytes,
build_settings.cobalt_image_cache_size_in_bytes,
CalculateImageCacheSize(ui_resolution));
EnsureValuePositive(image_cache_size_in_bytes_.get());
image_cache_size_in_bytes_->set_memory_type(MemorySetting::kGPU);
// ImageCache releases memory linearly until a progress value of 75%, then
// it will not reduce memory any more. It will also clamp at 100% and won't
// be increased beyond that.
image_cache_size_in_bytes_->set_memory_scaling_function(
MakeLinearMemoryScaler(.75, 1.0));
// Set javascript gc threshold
JavaScriptGcThresholdSetting* js_setting = new JavaScriptGcThresholdSetting;
SetMemorySetting<IntSetting, int64_t>(
command_line_settings.javascript_garbage_collection_threshold_in_bytes,
build_settings.javascript_garbage_collection_threshold_in_bytes,
kDefaultJsGarbageCollectionThresholdSize, js_setting);
EnsureValuePositive(js_setting);
js_setting->PostInit();
javascript_gc_threshold_in_bytes_.reset(js_setting);
// Set the misc cobalt size to a specific size.
misc_cobalt_cpu_size_in_bytes_.reset(
new IntSetting("misc_cobalt_cpu_size_in_bytes"));
misc_cobalt_cpu_size_in_bytes_->set_value(
MemorySetting::kAutoSet, kMiscCobaltCpuSizeInBytes);
// Set the misc cobalt size to a specific size.
misc_cobalt_gpu_size_in_bytes_.reset(
new IntSetting("misc_cobalt_gpu_size_in_bytes"));
misc_cobalt_gpu_size_in_bytes_->set_memory_type(MemorySetting::kGPU);
misc_cobalt_gpu_size_in_bytes_->set_value(
MemorySetting::kAutoSet, CalculateMiscCobaltGpuSize(ui_resolution));
// Set remote_type_face_cache size.
remote_typeface_cache_size_in_bytes_ =
CreateMemorySetting<IntSetting, int64_t>(
switches::kRemoteTypefaceCacheSizeInBytes,
command_line_settings.remote_typeface_cache_capacity_in_bytes,
build_settings.remote_typeface_cache_capacity_in_bytes,
kDefaultRemoteTypeFaceCacheSize);
EnsureValuePositive(remote_typeface_cache_size_in_bytes_.get());
// Skia atlas texture dimensions.
skia_atlas_texture_dimensions_.reset(new SkiaGlyphAtlasTextureSetting());
SetMemorySetting(command_line_settings.skia_texture_atlas_dimensions,
build_settings.skia_texture_atlas_dimensions,
CalculateSkiaGlyphAtlasTextureSize(ui_resolution),
skia_atlas_texture_dimensions_.get());
EnsureValuePositive(skia_atlas_texture_dimensions_.get());
// Not available for non-blitter platforms.
if (build_settings.has_blitter) {
skia_atlas_texture_dimensions_->set_memory_type(
MemorySetting::kNotApplicable);
} else {
// Skia always uses gpu memory, when enabled.
skia_atlas_texture_dimensions_->set_memory_type(MemorySetting::kGPU);
}
EnsureValuePositive(skia_atlas_texture_dimensions_.get());
EnsureTwoBytesPerPixel(skia_atlas_texture_dimensions_.get());
// Set skia_cache_size_in_bytes
skia_cache_size_in_bytes_ = CreateMemorySetting<IntSetting, int64_t>(
switches::kSkiaCacheSizeInBytes,
command_line_settings.skia_cache_size_in_bytes,
build_settings.skia_cache_size_in_bytes,
CalculateSkiaCacheSize(ui_resolution));
// Not available for blitter platforms.
if (build_settings.has_blitter) {
skia_cache_size_in_bytes_->set_memory_type(MemorySetting::kNotApplicable);
} else {
// Skia always uses gpu memory, when enabled.
skia_cache_size_in_bytes_->set_memory_type(MemorySetting::kGPU);
}
EnsureValuePositive(skia_cache_size_in_bytes_.get());
// Set software_surface_cache_size_in_bytes
software_surface_cache_size_in_bytes_ =
CreateMemorySetting<IntSetting, int64_t>(
switches::kSoftwareSurfaceCacheSizeInBytes,
command_line_settings.software_surface_cache_size_in_bytes,
build_settings.software_surface_cache_size_in_bytes,
CalculateSoftwareSurfaceCacheSizeInBytes(ui_resolution));
// Blitter only feature.
if (!build_settings.has_blitter) {
software_surface_cache_size_in_bytes_->set_memory_type(
MemorySetting::kNotApplicable);
}
EnsureValuePositive(software_surface_cache_size_in_bytes_.get());
// Set offscreen_target_cache_size_in_bytes (relevant to the direct-gles
// rasterizer).
offscreen_target_cache_size_in_bytes_ =
CreateMemorySetting<IntSetting, int64_t>(
switches::kOffscreenTargetCacheSizeInBytes,
command_line_settings.offscreen_target_cache_size_in_bytes,
build_settings.offscreen_target_cache_size_in_bytes,
CalculateOffscreenTargetCacheSizeInBytes(ui_resolution));
offscreen_target_cache_size_in_bytes_->set_memory_scaling_function(
MakeLinearMemoryScaler(0.25, 1.0));
#if defined(COBALT_FORCE_DIRECT_GLES_RASTERIZER)
offscreen_target_cache_size_in_bytes_->set_memory_type(MemorySetting::kGPU);
#else
offscreen_target_cache_size_in_bytes_->set_memory_type(
MemorySetting::kNotApplicable);
#endif
EnsureValuePositive(offscreen_target_cache_size_in_bytes_.get());
// Final stage: Check that all constraining functions are monotonically
// increasing.
const std::vector<const MemorySetting*> all_memory_settings =
AllMemorySettings();
for (size_t i = 0; i < all_memory_settings.size(); ++i) {
CheckConstrainingValues(*all_memory_settings[i]);
}
}
} // namespace memory_settings
} // namespace browser
} // namespace cobalt