Add build flag to force memcg to the v2 cgroup hierarchy

This flag prevents memcg from being mounted as a v1 hierarchy, even if
cgroups.json specifies it should be mounted as v1. It will activate
memcg in the v2 hierarchy even if cgroups.json does not specify that it
should be activated in the v2 hierarchy.

The cgroup_disable=memory kernel command line argument will still
prevent memcg from being activated, as this forced memcg controller
is marked as optional.

Bug: 327480673
Change-Id: Iad2491dd0c1576156ee2346928d041d85af890f0
This commit is contained in:
T.J. Mercier 2024-03-05 21:55:15 +00:00
parent d1e048f956
commit f8901767e6
4 changed files with 126 additions and 11 deletions

View file

@ -2,9 +2,28 @@ package {
default_applicable_licenses: ["Android-Apache-2.0"],
}
cc_defaults {
name: "libprocessgroup_defaults",
soong_config_module_type {
name: "libprocessgroup_flag_aware_cc_defaults",
module_type: "cc_defaults",
config_namespace: "ANDROID",
bool_variables: [
"memcg_v2_force_enabled",
],
properties: [
"cflags",
],
}
libprocessgroup_flag_aware_cc_defaults {
name: "libprocessgroup_build_flags_cc",
cpp_std: "gnu++20",
soong_config_variables: {
memcg_v2_force_enabled: {
cflags: [
"-DMEMCG_V2_FORCE_ENABLED=true",
],
},
},
}
cc_library_headers {
@ -67,7 +86,7 @@ cc_library {
export_header_lib_headers: [
"libprocessgroup_headers",
],
defaults: ["libprocessgroup_defaults"],
defaults: ["libprocessgroup_build_flags_cc"],
apex_available: [
"//apex_available:platform",
"//apex_available:anyapex",
@ -78,7 +97,7 @@ cc_library {
cc_test {
name: "task_profiles_test",
host_supported: true,
defaults: ["libprocessgroup_defaults"],
defaults: ["libprocessgroup_build_flags_cc"],
srcs: [
"task_profiles_test.cpp",
],

View file

@ -0,0 +1,29 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* 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.
*/
#pragma once
#ifndef MEMCG_V2_FORCE_ENABLED
#define MEMCG_V2_FORCE_ENABLED false
#endif
namespace android::libprocessgroup_flags {
inline consteval bool force_memcg_v2() {
return MEMCG_V2_FORCE_ENABLED;
}
} // namespace android::libprocessgroup_flags

View file

@ -41,8 +41,5 @@ cc_library_shared {
export_header_lib_headers: [
"libprocessgroup_headers",
],
cflags: [
"-Wall",
"-Werror",
],
defaults: ["libprocessgroup_build_flags_cc"],
}

View file

@ -29,7 +29,7 @@
#include <time.h>
#include <unistd.h>
#include <regex>
#include <optional>
#include <android-base/file.h>
#include <android-base/logging.h>
@ -43,6 +43,7 @@
#include <processgroup/processgroup.h>
#include <processgroup/setup.h>
#include "../build_flags.h"
#include "cgroup_descriptor.h"
using android::base::GetUintProperty;
@ -57,6 +58,8 @@ static constexpr const char* CGROUPS_DESC_VENDOR_FILE = "/vendor/etc/cgroups.jso
static constexpr const char* TEMPLATE_CGROUPS_DESC_API_FILE = "/etc/task_profiles/cgroups_%u.json";
static const std::string CGROUP_V2_ROOT_DEFAULT = "/sys/fs/cgroup";
static bool ChangeDirModeAndOwner(const std::string& path, mode_t mode, const std::string& uid,
const std::string& gid, bool permissive_mode = false) {
uid_t pw_uid = -1;
@ -182,6 +185,8 @@ static void MergeCgroupToDescriptors(std::map<std::string, CgroupDescriptor>* de
}
}
static const bool force_memcg_v2 = android::libprocessgroup_flags::force_memcg_v2();
static bool ReadDescriptorsFromFile(const std::string& file_name,
std::map<std::string, CgroupDescriptor>* descriptors) {
std::vector<CgroupDescriptor> result;
@ -205,22 +210,41 @@ static bool ReadDescriptorsFromFile(const std::string& file_name,
const Json::Value& cgroups = root["Cgroups"];
for (Json::Value::ArrayIndex i = 0; i < cgroups.size(); ++i) {
std::string name = cgroups[i]["Controller"].asString();
if (force_memcg_v2 && name == "memory") continue;
MergeCgroupToDescriptors(descriptors, cgroups[i], name, "", 1);
}
}
bool memcgv2_present = false;
std::string root_path;
if (root.isMember("Cgroups2")) {
const Json::Value& cgroups2 = root["Cgroups2"];
std::string root_path = cgroups2["Path"].asString();
root_path = cgroups2["Path"].asString();
MergeCgroupToDescriptors(descriptors, cgroups2, CGROUPV2_HIERARCHY_NAME, "", 2);
const Json::Value& childGroups = cgroups2["Controllers"];
for (Json::Value::ArrayIndex i = 0; i < childGroups.size(); ++i) {
std::string name = childGroups[i]["Controller"].asString();
if (force_memcg_v2 && name == "memory") memcgv2_present = true;
MergeCgroupToDescriptors(descriptors, childGroups[i], name, root_path, 2);
}
}
if (force_memcg_v2 && !memcgv2_present) {
LOG(INFO) << "Forcing memcg to v2 hierarchy";
Json::Value memcgv2;
memcgv2["Controller"] = "memory";
memcgv2["NeedsActivation"] = true;
memcgv2["Path"] = ".";
memcgv2["Optional"] = true; // In case of cgroup_disabled=memory, so we can still boot
MergeCgroupToDescriptors(descriptors, memcgv2, "memory",
root_path.empty() ? CGROUP_V2_ROOT_DEFAULT : root_path, 2);
}
return true;
}
@ -308,7 +332,8 @@ static bool ActivateV2CgroupController(const CgroupDescriptor& descriptor) {
if (!base::WriteStringToFile(str, path)) {
if (IsOptionalController(controller)) {
PLOG(INFO) << "Failed to activate optional controller " << controller->name();
PLOG(INFO) << "Failed to activate optional controller " << controller->name()
<< " at " << path;
return true;
}
PLOG(ERROR) << "Failed to activate controller " << controller->name();
@ -424,6 +449,40 @@ void CgroupDescriptor::set_mounted(bool mounted) {
} // namespace cgrouprc
} // namespace android
static std::optional<bool> MGLRUDisabled() {
const std::string file_name = "/sys/kernel/mm/lru_gen/enabled";
std::string content;
if (!android::base::ReadFileToString(file_name, &content)) {
PLOG(ERROR) << "Failed to read MGLRU state from " << file_name;
return {};
}
return content == "0x0000";
}
static std::optional<bool> MEMCGDisabled(
const std::map<std::string, android::cgrouprc::CgroupDescriptor>& descriptors) {
std::string cgroup_v2_root = android::cgrouprc::CGROUP_V2_ROOT_DEFAULT;
const auto it = descriptors.find(CGROUPV2_HIERARCHY_NAME);
if (it == descriptors.end()) {
LOG(WARNING) << "No Cgroups2 path found in cgroups.json. Vendor has modified Android, and "
<< "kernel memory use will be higher than intended.";
} else if (it->second.controller()->path() != cgroup_v2_root) {
cgroup_v2_root = it->second.controller()->path();
}
const std::string file_name = cgroup_v2_root + "/cgroup.controllers";
std::string content;
if (!android::base::ReadFileToString(file_name, &content)) {
PLOG(ERROR) << "Failed to read cgroup controllers from " << file_name;
return {};
}
// If we've forced memcg to v2 and it's not available, then it could only have been disabled
// on the kernel command line (GKI sets CONFIG_MEMCG).
return content.find("memory") == std::string::npos;
}
bool CgroupSetup() {
using namespace android::cgrouprc;
@ -457,6 +516,17 @@ bool CgroupSetup() {
}
}
if (force_memcg_v2) {
if (MGLRUDisabled().value_or(false)) {
LOG(WARNING) << "Memcg forced to v2 hierarchy with MGLRU disabled! "
<< "Global reclaim performance will suffer.";
}
if (MEMCGDisabled(descriptors).value_or(false)) {
LOG(WARNING) << "Memcg forced to v2 hierarchy while memcg is disabled by kernel "
<< "command line!";
}
}
// mkdir <CGROUPS_RC_DIR> 0711 system system
if (!Mkdir(android::base::Dirname(CGROUPS_RC_PATH), 0711, "system", "system")) {
LOG(ERROR) << "Failed to create directory for " << CGROUPS_RC_PATH << " file";