libprocessgroup: Remove cgroup.rc file
The cgroup.rc file was introduced in 192aee782 ("libprocessgroup: Add
support for task profiles") back with the initial support for task
profiles. It was intended to optimize performance associated with cgroup
operations. However over time, supporting this file led to making
libprocessgroup code more complicated (such as the cgrouprc LLNDK
interface), and the file ended up getting mmaped into nearly every
process on Android even though only a handful of them actually use it.
Replacing this file with reading and parsing of cgroup information on
demand allows us to simplify and shrink libprocessgroup, and eliminates
thousands of unused mappings without negatively affecting boot time or
other performance metrics.
Bug: 349105928
Test: Verified with memcg v2 and MaxActivationDepth 1 on Cuttlefish, Raven, and Mokey
Change-Id: Ic3f01fdf7fda89a56ab80657e1cf4573156273e6
diff --git a/libprocessgroup/util/util.cpp b/libprocessgroup/util/util.cpp
index 9b88a22..bff4c6f 100644
--- a/libprocessgroup/util/util.cpp
+++ b/libprocessgroup/util/util.cpp
@@ -18,9 +18,33 @@
#include <algorithm>
#include <iterator>
+#include <optional>
+#include <string_view>
+
+#include <mntent.h>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <json/reader.h>
+#include <json/value.h>
+
+#include "../build_flags.h"
+#include "../internal.h"
+
+using android::base::GetUintProperty;
namespace {
+constexpr const char* CGROUPS_DESC_FILE = "/etc/cgroups.json";
+constexpr const char* CGROUPS_DESC_VENDOR_FILE = "/vendor/etc/cgroups.json";
+constexpr const char* TEMPLATE_CGROUPS_DESC_API_FILE = "/etc/task_profiles/cgroups_%u.json";
+
+// This should match the publicly declared value in processgroup.h,
+// but we don't want this library to depend on libprocessgroup.
+constexpr std::string CGROUPV2_HIERARCHY_NAME_INTERNAL = "cgroup2";
+
const char SEP = '/';
std::string DeduplicateAndTrimSeparators(const std::string& path) {
@@ -42,9 +66,135 @@
return ret;
}
+void MergeCgroupToDescriptors(CgroupDescriptorMap* descriptors, const Json::Value& cgroup,
+ const std::string& name, const std::string& root_path,
+ int cgroups_version) {
+ const std::string cgroup_path = cgroup["Path"].asString();
+ std::string path;
+
+ if (!root_path.empty()) {
+ path = root_path;
+ if (cgroup_path != ".") {
+ path += "/";
+ path += cgroup_path;
+ }
+ } else {
+ path = cgroup_path;
+ }
+
+ uint32_t controller_flags = 0;
+
+ if (cgroup["NeedsActivation"].isBool() && cgroup["NeedsActivation"].asBool()) {
+ controller_flags |= CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION;
+ }
+
+ if (cgroup["Optional"].isBool() && cgroup["Optional"].asBool()) {
+ controller_flags |= CGROUPRC_CONTROLLER_FLAG_OPTIONAL;
+ }
+
+ uint32_t max_activation_depth = UINT32_MAX;
+ if (cgroup.isMember("MaxActivationDepth")) {
+ max_activation_depth = cgroup["MaxActivationDepth"].asUInt();
+ }
+
+ CgroupDescriptor descriptor(
+ cgroups_version, name, path, std::strtoul(cgroup["Mode"].asString().c_str(), 0, 8),
+ cgroup["UID"].asString(), cgroup["GID"].asString(), controller_flags,
+ max_activation_depth);
+
+ auto iter = descriptors->find(name);
+ if (iter == descriptors->end()) {
+ descriptors->emplace(name, descriptor);
+ } else {
+ iter->second = descriptor;
+ }
+}
+
+bool ReadDescriptorsFromFile(const std::string& file_name, CgroupDescriptorMap* descriptors) {
+ static constexpr bool force_memcg_v2 = android::libprocessgroup_flags::force_memcg_v2();
+ std::vector<CgroupDescriptor> result;
+ std::string json_doc;
+
+ if (!android::base::ReadFileToString(file_name, &json_doc)) {
+ PLOG(ERROR) << "Failed to read task profiles from " << file_name;
+ return false;
+ }
+
+ Json::CharReaderBuilder builder;
+ std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
+ Json::Value root;
+ std::string errorMessage;
+ if (!reader->parse(&*json_doc.begin(), &*json_doc.end(), &root, &errorMessage)) {
+ LOG(ERROR) << "Failed to parse cgroups description: " << errorMessage;
+ return false;
+ }
+
+ if (root.isMember("Cgroups")) {
+ 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"];
+ root_path = cgroups2["Path"].asString();
+ MergeCgroupToDescriptors(descriptors, cgroups2, CGROUPV2_HIERARCHY_NAME_INTERNAL, "", 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;
+}
+
+using MountDir = std::string;
+using MountOpts = std::string;
+static std::optional<std::map<MountDir, MountOpts>> ReadCgroupV1Mounts() {
+ FILE* fp = setmntent("/proc/mounts", "r");
+ if (fp == nullptr) {
+ PLOG(ERROR) << "Failed to read mounts";
+ return std::nullopt;
+ }
+
+ std::map<MountDir, MountOpts> mounts;
+ const std::string_view CGROUP_V1_TYPE = "cgroup";
+ for (mntent* mentry = getmntent(fp); mentry != nullptr; mentry = getmntent(fp)) {
+ if (mentry->mnt_type && CGROUP_V1_TYPE == mentry->mnt_type &&
+ mentry->mnt_dir && mentry->mnt_opts) {
+ mounts[mentry->mnt_dir] = mentry->mnt_opts;
+ }
+ }
+ endmntent(fp);
+
+ return mounts;
+}
+
} // anonymous namespace
-namespace util {
unsigned int GetCgroupDepth(const std::string& controller_root, const std::string& cgroup_path) {
const std::string deduped_root = DeduplicateAndTrimSeparators(controller_root);
@@ -56,4 +206,47 @@
return std::count(deduped_path.begin() + deduped_root.size(), deduped_path.end(), SEP);
}
-} // namespace util
+bool ReadDescriptors(CgroupDescriptorMap* descriptors) {
+ // load system cgroup descriptors
+ if (!ReadDescriptorsFromFile(CGROUPS_DESC_FILE, descriptors)) {
+ return false;
+ }
+
+ // load API-level specific system cgroups descriptors if available
+ unsigned int api_level = GetUintProperty<unsigned int>("ro.product.first_api_level", 0);
+ if (api_level > 0) {
+ std::string api_cgroups_path =
+ android::base::StringPrintf(TEMPLATE_CGROUPS_DESC_API_FILE, api_level);
+ if (!access(api_cgroups_path.c_str(), F_OK) || errno != ENOENT) {
+ if (!ReadDescriptorsFromFile(api_cgroups_path, descriptors)) {
+ return false;
+ }
+ }
+ }
+
+ // load vendor cgroup descriptors if the file exists
+ if (!access(CGROUPS_DESC_VENDOR_FILE, F_OK) &&
+ !ReadDescriptorsFromFile(CGROUPS_DESC_VENDOR_FILE, descriptors)) {
+ return false;
+ }
+
+ // check for v1 mount/usability status
+ std::optional<std::map<MountDir, MountOpts>> v1Mounts;
+ for (auto& [name, descriptor] : *descriptors) {
+ const CgroupController* const controller = descriptor.controller();
+
+ if (controller->version() != 1) continue;
+
+ // Read only once, and only if we have at least one v1 controller
+ if (!v1Mounts) {
+ v1Mounts = ReadCgroupV1Mounts();
+ if (!v1Mounts) return false;
+ }
+
+ if (const auto it = v1Mounts->find(controller->path()); it != v1Mounts->end()) {
+ if (it->second.contains(controller->name())) descriptor.set_mounted(true);
+ }
+ }
+
+ return true;
+}